Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for LaCrosse sensor components."""
2 
3 from __future__ import annotations
4 
5 from datetime import datetime, timedelta
6 import logging
7 from typing import Any
8 
9 import pylacrosse
10 from serial import SerialException
11 import voluptuous as vol
12 
14  ENTITY_ID_FORMAT,
15  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
16  SensorDeviceClass,
17  SensorEntity,
18  SensorStateClass,
19 )
20 from homeassistant.const import (
21  CONF_DEVICE,
22  CONF_ID,
23  CONF_NAME,
24  CONF_SENSORS,
25  CONF_TYPE,
26  EVENT_HOMEASSISTANT_STOP,
27  PERCENTAGE,
28  UnitOfTemperature,
29 )
30 from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
32 from homeassistant.helpers.entity import async_generate_entity_id
33 from homeassistant.helpers.entity_platform import AddEntitiesCallback
34 from homeassistant.helpers.event import async_track_point_in_utc_time
35 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
36 from homeassistant.util import dt as dt_util
37 
38 _LOGGER = logging.getLogger(__name__)
39 
40 CONF_BAUD = "baud"
41 CONF_DATARATE = "datarate"
42 CONF_EXPIRE_AFTER = "expire_after"
43 CONF_FREQUENCY = "frequency"
44 CONF_JEELINK_LED = "led"
45 CONF_TOGGLE_INTERVAL = "toggle_interval"
46 CONF_TOGGLE_MASK = "toggle_mask"
47 
48 DEFAULT_DEVICE = "/dev/ttyUSB0"
49 DEFAULT_BAUD = 57600
50 DEFAULT_EXPIRE_AFTER = 300
51 
52 TYPES = ["battery", "humidity", "temperature"]
53 
54 SENSOR_SCHEMA = vol.Schema(
55  {
56  vol.Required(CONF_ID): cv.positive_int,
57  vol.Required(CONF_TYPE): vol.In(TYPES),
58  vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int,
59  vol.Optional(CONF_NAME): cv.string,
60  }
61 )
62 
63 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
64  {
65  vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA),
66  vol.Optional(CONF_BAUD, default=DEFAULT_BAUD): cv.positive_int,
67  vol.Optional(CONF_DATARATE): cv.positive_int,
68  vol.Optional(CONF_DEVICE, default=DEFAULT_DEVICE): cv.string,
69  vol.Optional(CONF_FREQUENCY): cv.positive_int,
70  vol.Optional(CONF_JEELINK_LED): cv.boolean,
71  vol.Optional(CONF_TOGGLE_INTERVAL): cv.positive_int,
72  vol.Optional(CONF_TOGGLE_MASK): cv.positive_int,
73  }
74 )
75 
76 
78  hass: HomeAssistant,
79  config: ConfigType,
80  add_entities: AddEntitiesCallback,
81  discovery_info: DiscoveryInfoType | None = None,
82 ) -> None:
83  """Set up the LaCrosse sensors."""
84  usb_device: str = config[CONF_DEVICE]
85  baud: int = config[CONF_BAUD]
86  expire_after: int | None = config.get(CONF_EXPIRE_AFTER)
87 
88  _LOGGER.debug("%s %s", usb_device, baud)
89 
90  try:
91  lacrosse = pylacrosse.LaCrosse(usb_device, baud)
92  lacrosse.open()
93  except SerialException as exc:
94  _LOGGER.warning("Unable to open serial port: %s", exc)
95  return
96 
97  hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: lacrosse.close())
98 
99  if CONF_JEELINK_LED in config:
100  lacrosse.led_mode_state(config.get(CONF_JEELINK_LED))
101  if CONF_FREQUENCY in config:
102  lacrosse.set_frequency(config.get(CONF_FREQUENCY))
103  if CONF_DATARATE in config:
104  lacrosse.set_datarate(config.get(CONF_DATARATE))
105  if CONF_TOGGLE_INTERVAL in config:
106  lacrosse.set_toggle_interval(config.get(CONF_TOGGLE_INTERVAL))
107  if CONF_TOGGLE_MASK in config:
108  lacrosse.set_toggle_mask(config.get(CONF_TOGGLE_MASK))
109 
110  lacrosse.start_scan()
111 
112  sensors: list[LaCrosseSensor] = []
113  for device, device_config in config[CONF_SENSORS].items():
114  _LOGGER.debug("%s %s", device, device_config)
115 
116  typ: str = device_config[CONF_TYPE]
117  sensor_class = TYPE_CLASSES[typ]
118  name: str = device_config.get(CONF_NAME, device)
119 
120  sensors.append(
121  sensor_class(hass, lacrosse, device, name, expire_after, device_config)
122  )
123 
124  add_entities(sensors)
125 
126 
128  """Implementation of a Lacrosse sensor."""
129 
130  _temperature: float | None = None
131  _humidity: int | None = None
132  _low_battery: bool | None = None
133  _new_battery: bool | None = None
134 
135  def __init__(
136  self,
137  hass: HomeAssistant,
138  lacrosse: pylacrosse.LaCrosse,
139  device_id: str,
140  name: str,
141  expire_after: int | None,
142  config: ConfigType,
143  ) -> None:
144  """Initialize the sensor."""
145  self.hasshasshass = hass
147  ENTITY_ID_FORMAT, device_id, hass=hass
148  )
149  self._config_config = config
150  self._expire_after_expire_after = expire_after
151  self._expiration_trigger_expiration_trigger: CALLBACK_TYPE | None = None
152  self._attr_name_attr_name = name
153 
154  lacrosse.register_callback(
155  int(self._config_config["id"]), self._callback_lacrosse_callback_lacrosse, None
156  )
157 
158  @property
159  def extra_state_attributes(self) -> dict[str, Any]:
160  """Return the state attributes."""
161  return {
162  "low_battery": self._low_battery_low_battery,
163  "new_battery": self._new_battery_new_battery,
164  }
165 
167  self, lacrosse_sensor: pylacrosse.LaCrosseSensor, user_data: None
168  ) -> None:
169  """Handle a function that is called from pylacrosse with new values."""
170  if self._expire_after_expire_after is not None and self._expire_after_expire_after > 0:
171  # Reset old trigger
172  if self._expiration_trigger_expiration_trigger:
173  self._expiration_trigger_expiration_trigger()
174  self._expiration_trigger_expiration_trigger = None
175 
176  # Set new trigger
177  expiration_at = dt_util.utcnow() + timedelta(seconds=self._expire_after_expire_after)
178 
179  self._expiration_trigger_expiration_trigger = async_track_point_in_utc_time(
180  self.hasshasshass, self.value_is_expiredvalue_is_expired, expiration_at
181  )
182 
183  self._temperature_temperature = lacrosse_sensor.temperature
184  self._humidity_humidity = lacrosse_sensor.humidity
185  self._low_battery_low_battery = lacrosse_sensor.low_battery
186  self._new_battery_new_battery = lacrosse_sensor.new_battery
187 
188  @callback
189  def value_is_expired(self, *_: datetime) -> None:
190  """Triggered when value is expired."""
191  self._expiration_trigger_expiration_trigger = None
192  self.async_write_ha_stateasync_write_ha_state()
193 
194 
196  """Implementation of a Lacrosse temperature sensor."""
197 
198  _attr_device_class = SensorDeviceClass.TEMPERATURE
199  _attr_state_class = SensorStateClass.MEASUREMENT
200  _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
201 
202  @property
203  def native_value(self) -> float | None:
204  """Return the state of the sensor."""
205  return self._temperature_temperature
206 
207 
209  """Implementation of a Lacrosse humidity sensor."""
210 
211  _attr_native_unit_of_measurement = PERCENTAGE
212  _attr_state_class = SensorStateClass.MEASUREMENT
213  _attr_device_class = SensorDeviceClass.HUMIDITY
214 
215  @property
216  def native_value(self) -> int | None:
217  """Return the state of the sensor."""
218  return self._humidity_humidity
219 
220 
222  """Implementation of a Lacrosse battery sensor."""
223 
224  @property
225  def native_value(self) -> str | None:
226  """Return the state of the sensor."""
227  if self._low_battery_low_battery is None:
228  return None
229  if self._low_battery_low_battery is True:
230  return "low"
231  return "ok"
232 
233  @property
234  def icon(self) -> str:
235  """Icon to use in the frontend."""
236  if self._low_battery_low_battery is None:
237  return "mdi:battery-unknown"
238  if self._low_battery_low_battery is True:
239  return "mdi:battery-alert"
240  return "mdi:battery"
241 
242 
243 TYPE_CLASSES: dict[str, type[LaCrosseSensor]] = {
244  "temperature": LaCrosseTemperature,
245  "humidity": LaCrosseHumidity,
246  "battery": LaCrosseBattery,
247 }
None _callback_lacrosse(self, pylacrosse.LaCrosseSensor lacrosse_sensor, None user_data)
Definition: sensor.py:168
None __init__(self, HomeAssistant hass, pylacrosse.LaCrosse lacrosse, str device_id, str name, int|None expire_after, ConfigType config)
Definition: sensor.py:143
def add_entities(account, async_add_entities, tracked)
Definition: sensor.py:40
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:82
str async_generate_entity_id(str entity_id_format, str|None name, Iterable[str]|None current_ids=None, HomeAssistant|None hass=None)
Definition: entity.py:119
CALLBACK_TYPE async_track_point_in_utc_time(HomeAssistant hass, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action, datetime point_in_time)
Definition: event.py:1542