Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Sensor component for LaCrosse View."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass, replace
7 import logging
8 
9 from lacrosse_view import Sensor
10 
12  SensorDeviceClass,
13  SensorEntity,
14  SensorEntityDescription,
15  SensorStateClass,
16 )
17 from homeassistant.config_entries import ConfigEntry
18 from homeassistant.const import (
19  DEGREE,
20  PERCENTAGE,
21  UnitOfPrecipitationDepth,
22  UnitOfPressure,
23  UnitOfSpeed,
24  UnitOfTemperature,
25 )
26 from homeassistant.core import HomeAssistant
27 from homeassistant.helpers.device_registry import DeviceInfo
28 from homeassistant.helpers.entity_platform import AddEntitiesCallback
30  CoordinatorEntity,
31  DataUpdateCoordinator,
32 )
33 
34 from .const import DOMAIN
35 
36 _LOGGER = logging.getLogger(__name__)
37 
38 
39 @dataclass(frozen=True, kw_only=True)
41  """Description for LaCrosse View sensor."""
42 
43  value_fn: Callable[[Sensor, str], float | int | str | None]
44 
45 
46 def get_value(sensor: Sensor, field: str) -> float | int | str | None:
47  """Get the value of a sensor field."""
48  field_data = sensor.data.get(field)
49  if field_data is None:
50  return None
51  value = field_data["values"][-1]["s"]
52  try:
53  value = float(value)
54  except ValueError:
55  return str(value) # handle non-numericals
56  return int(value) if value.is_integer() else value
57 
58 
59 PARALLEL_UPDATES = 0
60 SENSOR_DESCRIPTIONS = {
61  "Temperature": LaCrosseSensorEntityDescription(
62  key="Temperature",
63  device_class=SensorDeviceClass.TEMPERATURE,
64  state_class=SensorStateClass.MEASUREMENT,
65  value_fn=get_value,
66  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
67  ),
69  key="Humidity",
70  device_class=SensorDeviceClass.HUMIDITY,
71  state_class=SensorStateClass.MEASUREMENT,
72  value_fn=get_value,
73  native_unit_of_measurement=PERCENTAGE,
74  ),
76  key="HeatIndex",
77  translation_key="heat_index",
78  device_class=SensorDeviceClass.TEMPERATURE,
79  state_class=SensorStateClass.MEASUREMENT,
80  value_fn=get_value,
81  native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
82  ),
84  key="WindSpeed",
85  state_class=SensorStateClass.MEASUREMENT,
86  value_fn=get_value,
87  native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
88  device_class=SensorDeviceClass.WIND_SPEED,
89  ),
91  key="Rain",
92  state_class=SensorStateClass.MEASUREMENT,
93  value_fn=get_value,
94  native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
95  device_class=SensorDeviceClass.PRECIPITATION,
96  ),
97  "WindHeading": LaCrosseSensorEntityDescription(
98  key="WindHeading",
99  translation_key="wind_heading",
100  value_fn=get_value,
101  native_unit_of_measurement=DEGREE,
102  ),
104  key="WetDry",
105  translation_key="wet_dry",
106  value_fn=get_value,
107  ),
109  key="Flex",
110  translation_key="flex",
111  value_fn=get_value,
112  ),
113  "BarometricPressure": LaCrosseSensorEntityDescription(
114  key="BarometricPressure",
115  translation_key="barometric_pressure",
116  state_class=SensorStateClass.MEASUREMENT,
117  value_fn=get_value,
118  device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
119  native_unit_of_measurement=UnitOfPressure.HPA,
120  ),
121  "FeelsLike": LaCrosseSensorEntityDescription(
122  key="FeelsLike",
123  translation_key="feels_like",
124  state_class=SensorStateClass.MEASUREMENT,
125  value_fn=get_value,
126  device_class=SensorDeviceClass.TEMPERATURE,
127  native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
128  ),
129  "WindChill": LaCrosseSensorEntityDescription(
130  key="WindChill",
131  translation_key="wind_chill",
132  state_class=SensorStateClass.MEASUREMENT,
133  value_fn=get_value,
134  device_class=SensorDeviceClass.TEMPERATURE,
135  native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
136  ),
137 }
138 # map of API returned unit of measurement strings to their corresponding unit of measurement
139 UNIT_OF_MEASUREMENT_MAP = {
140  "degrees_celsius": UnitOfTemperature.CELSIUS,
141  "degrees_fahrenheit": UnitOfTemperature.FAHRENHEIT,
142  "inches": UnitOfPrecipitationDepth.INCHES,
143  "millimeters": UnitOfPrecipitationDepth.MILLIMETERS,
144  "kilometers_per_hour": UnitOfSpeed.KILOMETERS_PER_HOUR,
145  "miles_per_hour": UnitOfSpeed.MILES_PER_HOUR,
146 }
147 
148 
150  hass: HomeAssistant,
151  entry: ConfigEntry,
152  async_add_entities: AddEntitiesCallback,
153 ) -> None:
154  """Set up LaCrosse View from a config entry."""
155  coordinator: DataUpdateCoordinator[list[Sensor]] = hass.data[DOMAIN][
156  entry.entry_id
157  ]["coordinator"]
158  sensors: list[Sensor] = coordinator.data
159 
160  sensor_list = []
161  for i, sensor in enumerate(sensors):
162  for field in sensor.sensor_field_names:
163  description = SENSOR_DESCRIPTIONS.get(field)
164  if description is None:
165  message = (
166  f"Unsupported sensor field: {field}\nPlease create an issue on "
167  "GitHub."
168  " https://github.com/home-assistant/core/issues/new?assignees="
169  "&labels=&template=bug_report.yml&integration_name=LaCrosse%20View"
170  "&integration_link="
171  "https://www.home-assistant.io/integrations/lacrosse_view/"
172  "&additional_information="
173  f"Field:%20{field}%0ASensor%20Model:%20{sensor.model}"
174  f"&title=LaCrosse%20View%20Unsupported%20sensor%20field:%20{field}"
175  )
176 
177  _LOGGER.warning(message)
178  continue
179 
180  # if the API returns a different unit of measurement from the description, update it
181  if sensor.data.get(field) is not None:
182  native_unit_of_measurement = UNIT_OF_MEASUREMENT_MAP.get(
183  sensor.data[field].get("unit")
184  )
185 
186  if native_unit_of_measurement is not None:
187  description = replace(
188  description,
189  native_unit_of_measurement=native_unit_of_measurement,
190  )
191 
192  sensor_list.append(
194  coordinator=coordinator,
195  description=description,
196  sensor=sensor,
197  index=i,
198  )
199  )
200 
201  async_add_entities(sensor_list)
202 
203 
205  CoordinatorEntity[DataUpdateCoordinator[list[Sensor]]], SensorEntity
206 ):
207  """LaCrosse View sensor."""
208 
209  entity_description: LaCrosseSensorEntityDescription
210  _attr_has_entity_name = True
211 
212  def __init__(
213  self,
214  description: LaCrosseSensorEntityDescription,
215  coordinator: DataUpdateCoordinator[list[Sensor]],
216  sensor: Sensor,
217  index: int,
218  ) -> None:
219  """Initialize."""
220  super().__init__(coordinator)
221 
222  self.entity_descriptionentity_description = description
223  self._attr_unique_id_attr_unique_id = f"{sensor.sensor_id}-{description.key}"
224  self._attr_device_info_attr_device_info = DeviceInfo(
225  identifiers={(DOMAIN, sensor.sensor_id)},
226  name=sensor.name,
227  manufacturer="LaCrosse Technology",
228  model=sensor.model,
229  via_device=(DOMAIN, sensor.location.id),
230  )
231  self.indexindex = index
232 
233  @property
234  def native_value(self) -> int | float | str | None:
235  """Return the sensor value."""
236  return self.entity_descriptionentity_description.value_fn(
237  self.coordinator.data[self.indexindex], self.entity_descriptionentity_description.key
238  )
239 
240  @property
241  def available(self) -> bool:
242  """Return True if entity is available."""
243  return (
244  super().available
245  and self.entity_descriptionentity_description.key in self.coordinator.data[self.indexindex].data
246  )
None __init__(self, LaCrosseSensorEntityDescription description, DataUpdateCoordinator[list[Sensor]] coordinator, Sensor sensor, int index)
Definition: sensor.py:218
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
float|int|str|None get_value(Sensor sensor, str field)
Definition: sensor.py:46
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:153