Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Representation of Venstar sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from typing import Any
8 
10  SensorDeviceClass,
11  SensorEntity,
12  SensorEntityDescription,
13  SensorStateClass,
14 )
15 from homeassistant.config_entries import ConfigEntry
16 from homeassistant.const import (
17  CONCENTRATION_PARTS_PER_MILLION,
18  PERCENTAGE,
19  UnitOfTemperature,
20  UnitOfTime,
21 )
22 from homeassistant.core import HomeAssistant
23 from homeassistant.helpers.entity import Entity
24 from homeassistant.helpers.entity_platform import AddEntitiesCallback
25 
26 from .const import DOMAIN
27 from .coordinator import VenstarDataUpdateCoordinator
28 from .entity import VenstarEntity
29 
30 RUNTIME_HEAT1 = "heat1"
31 RUNTIME_HEAT2 = "heat2"
32 RUNTIME_COOL1 = "cool1"
33 RUNTIME_COOL2 = "cool2"
34 RUNTIME_AUX1 = "aux1"
35 RUNTIME_AUX2 = "aux2"
36 RUNTIME_FC = "fc"
37 RUNTIME_OV = "ov"
38 
39 RUNTIME_DEVICES = [
40  RUNTIME_HEAT1,
41  RUNTIME_HEAT2,
42  RUNTIME_COOL1,
43  RUNTIME_COOL2,
44  RUNTIME_AUX1,
45  RUNTIME_AUX2,
46  RUNTIME_FC,
47  RUNTIME_OV,
48 ]
49 
50 RUNTIME_ATTRIBUTES = {
51  RUNTIME_HEAT1: "Heating Stage 1",
52  RUNTIME_HEAT2: "Heating Stage 2",
53  RUNTIME_COOL1: "Cooling Stage 1",
54  RUNTIME_COOL2: "Cooling Stage 2",
55  RUNTIME_AUX1: "Aux Stage 1",
56  RUNTIME_AUX2: "Aux Stage 2",
57  RUNTIME_FC: "Free Cooling",
58  RUNTIME_OV: "Override",
59 }
60 
61 SCHEDULE_PARTS: dict[int, str] = {
62  0: "morning",
63  1: "day",
64  2: "evening",
65  3: "night",
66  255: "inactive",
67 }
68 
69 STAGES: dict[int, str] = {0: "idle", 1: "first_stage", 2: "second_stage"}
70 
71 
72 @dataclass(frozen=True, kw_only=True)
74  """Base description of a Sensor entity."""
75 
76  value_fn: Callable[[VenstarDataUpdateCoordinator, str], Any]
77  name_fn: Callable[[str], str] | None
78  uom_fn: Callable[[VenstarDataUpdateCoordinator], str | None]
79 
80 
82  hass: HomeAssistant,
83  config_entry: ConfigEntry,
84  async_add_entities: AddEntitiesCallback,
85 ) -> None:
86  """Set up Venstar device sensors based on a config entry."""
87  coordinator: VenstarDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
88  entities: list[Entity] = []
89 
90  if sensors := coordinator.client.get_sensor_list():
91  for sensor_name in sensors:
92  entities.extend(
93  [
94  VenstarSensor(coordinator, config_entry, description, sensor_name)
95  for description in SENSOR_ENTITIES
96  if coordinator.client.get_sensor(sensor_name, description.key)
97  is not None
98  ]
99  )
100 
101  runtimes = coordinator.runtimes[-1]
102  for sensor_name in runtimes:
103  if sensor_name in RUNTIME_DEVICES:
104  entities.append(
106  coordinator, config_entry, RUNTIME_ENTITY, sensor_name
107  )
108  )
109  entities.extend(
110  VenstarSensor(coordinator, config_entry, description, sensor_name)
111  for description in CONSUMABLE_ENTITIES
112  if description.key == sensor_name
113  )
114 
115  for description in INFO_ENTITIES:
116  try:
117  # just checking if the key exists
118  coordinator.client.get_info(description.key)
119  except KeyError:
120  continue
121  entities.append(
122  VenstarSensor(coordinator, config_entry, description, description.key)
123  )
124 
125  if entities:
126  async_add_entities(entities)
127 
128 
129 def temperature_unit(coordinator: VenstarDataUpdateCoordinator) -> str:
130  """Return the correct unit for temperature."""
131  unit = UnitOfTemperature.CELSIUS
132  if coordinator.client.tempunits == coordinator.client.TEMPUNITS_F:
133  unit = UnitOfTemperature.FAHRENHEIT
134  return unit
135 
136 
138  """Base class for a Venstar sensor."""
139 
140  entity_description: VenstarSensorEntityDescription
141 
142  def __init__(
143  self,
144  coordinator: VenstarDataUpdateCoordinator,
145  config: ConfigEntry,
146  entity_description: VenstarSensorEntityDescription,
147  sensor_name: str,
148  ) -> None:
149  """Initialize the sensor."""
150  super().__init__(coordinator, config)
151  self.entity_descriptionentity_description = entity_description
152  self.sensor_namesensor_name = sensor_name
153  if entity_description.name_fn:
154  self._attr_name_attr_name = entity_description.name_fn(sensor_name)
155  self._config_config_config = config
156 
157  @property
158  def unique_id(self):
159  """Return the unique id."""
160  return f"{self._config.entry_id}_{self.sensor_name.replace(' ', '_')}_{self.entity_description.key}"
161 
162  @property
163  def native_value(self) -> int:
164  """Return state of the sensor."""
165  return self.entity_descriptionentity_description.value_fn(self.coordinator, self.sensor_namesensor_name)
166 
167  @property
168  def native_unit_of_measurement(self) -> str | None:
169  """Return unit of measurement the value is expressed in."""
170  return self.entity_descriptionentity_description.uom_fn(self.coordinator)
171 
172 
173 SENSOR_ENTITIES: tuple[VenstarSensorEntityDescription, ...] = (
175  key="hum",
176  device_class=SensorDeviceClass.HUMIDITY,
177  state_class=SensorStateClass.MEASUREMENT,
178  uom_fn=lambda _: PERCENTAGE,
179  value_fn=lambda coordinator, sensor_name: coordinator.client.get_sensor(
180  sensor_name, "hum"
181  ),
182  name_fn=lambda sensor_name: f"{sensor_name} Humidity",
183  ),
185  key="temp",
186  device_class=SensorDeviceClass.TEMPERATURE,
187  state_class=SensorStateClass.MEASUREMENT,
188  uom_fn=temperature_unit,
189  value_fn=lambda coordinator, sensor_name: round(
190  float(coordinator.client.get_sensor(sensor_name, "temp")), 1
191  ),
192  name_fn=lambda sensor_name: f"{sensor_name.replace(' Temp', '')} Temperature",
193  ),
195  key="co2",
196  device_class=SensorDeviceClass.CO2,
197  state_class=SensorStateClass.MEASUREMENT,
198  uom_fn=lambda _: CONCENTRATION_PARTS_PER_MILLION,
199  value_fn=lambda coordinator, sensor_name: coordinator.client.get_sensor(
200  sensor_name, "co2"
201  ),
202  name_fn=lambda sensor_name: f"{sensor_name} CO2",
203  ),
205  key="iaq",
206  device_class=SensorDeviceClass.AQI,
207  state_class=SensorStateClass.MEASUREMENT,
208  uom_fn=lambda _: None,
209  value_fn=lambda coordinator, sensor_name: coordinator.client.get_sensor(
210  sensor_name, "iaq"
211  ),
212  name_fn=lambda sensor_name: f"{sensor_name} IAQ",
213  ),
215  key="battery",
216  device_class=SensorDeviceClass.BATTERY,
217  state_class=SensorStateClass.MEASUREMENT,
218  uom_fn=lambda _: PERCENTAGE,
219  value_fn=lambda coordinator, sensor_name: coordinator.client.get_sensor(
220  sensor_name, "battery"
221  ),
222  name_fn=lambda sensor_name: f"{sensor_name} Battery",
223  ),
224 )
225 
227  key="runtime",
228  state_class=SensorStateClass.MEASUREMENT,
229  uom_fn=lambda _: UnitOfTime.MINUTES,
230  value_fn=lambda coordinator, sensor_name: coordinator.runtimes[-1][sensor_name],
231  name_fn=lambda sensor_name: f"{RUNTIME_ATTRIBUTES[sensor_name]} Runtime",
232 )
233 
234 CONSUMABLE_ENTITIES: tuple[VenstarSensorEntityDescription, ...] = (
236  key="filterHours",
237  state_class=SensorStateClass.MEASUREMENT,
238  uom_fn=lambda _: UnitOfTime.HOURS,
239  value_fn=lambda coordinator, sensor_name: (
240  coordinator.runtimes[-1][sensor_name] / 100
241  ),
242  name_fn=None,
243  translation_key="filter_install_time",
244  ),
246  key="filterDays",
247  state_class=SensorStateClass.MEASUREMENT,
248  uom_fn=lambda _: UnitOfTime.DAYS,
249  value_fn=lambda coordinator, sensor_name: coordinator.runtimes[-1][sensor_name],
250  name_fn=None,
251  translation_key="filter_usage",
252  ),
253 )
254 
255 INFO_ENTITIES: tuple[VenstarSensorEntityDescription, ...] = (
257  key="schedulepart",
258  device_class=SensorDeviceClass.ENUM,
259  options=list(SCHEDULE_PARTS.values()),
260  translation_key="schedule_part",
261  uom_fn=lambda _: None,
262  value_fn=lambda coordinator, sensor_name: SCHEDULE_PARTS[
263  coordinator.client.get_info(sensor_name)
264  ],
265  name_fn=None,
266  ),
268  key="activestage",
269  device_class=SensorDeviceClass.ENUM,
270  options=list(STAGES.values()),
271  translation_key="active_stage",
272  uom_fn=lambda _: None,
273  value_fn=lambda coordinator, sensor_name: STAGES[
274  coordinator.client.get_info(sensor_name)
275  ],
276  name_fn=None,
277  ),
278 )
None __init__(self, VenstarDataUpdateCoordinator coordinator, ConfigEntry config, VenstarSensorEntityDescription entity_description, str sensor_name)
Definition: sensor.py:148
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:85
str temperature_unit(VenstarDataUpdateCoordinator coordinator)
Definition: sensor.py:129