Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for collecting data from the ARWN project."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 from homeassistant.components import mqtt
9 from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
10 from homeassistant.const import DEGREE, UnitOfPrecipitationDepth, UnitOfTemperature
11 from homeassistant.core import HomeAssistant, callback
12 from homeassistant.helpers.entity_platform import AddEntitiesCallback
13 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
14 from homeassistant.util import slugify
15 from homeassistant.util.json import json_loads_object
16 
17 _LOGGER = logging.getLogger(__name__)
18 
19 DOMAIN = "arwn"
20 
21 DATA_ARWN = "arwn"
22 TOPIC = "arwn/#"
23 
24 
25 def discover_sensors(topic: str, payload: dict[str, Any]) -> list[ArwnSensor] | None:
26  """Given a topic, dynamically create the right sensor type.
27 
28  Async friendly.
29  """
30  parts = topic.split("/")
31  unit = payload.get("units", "")
32  domain = parts[1]
33  if domain == "temperature":
34  name = parts[2]
35  if unit == "F":
36  unit = UnitOfTemperature.FAHRENHEIT
37  else:
38  unit = UnitOfTemperature.CELSIUS
39  return [
40  ArwnSensor(
41  topic, name, "temp", unit, device_class=SensorDeviceClass.TEMPERATURE
42  )
43  ]
44  if domain == "moisture":
45  name = f"{parts[2]} Moisture"
46  return [ArwnSensor(topic, name, "moisture", unit, "mdi:water-percent")]
47  if domain == "rain":
48  if len(parts) >= 3 and parts[2] == "today":
49  return [
50  ArwnSensor(
51  topic,
52  "Rain Since Midnight",
53  "since_midnight",
54  UnitOfPrecipitationDepth.INCHES,
55  device_class=SensorDeviceClass.PRECIPITATION,
56  )
57  ]
58  return [
59  ArwnSensor(
60  topic + "/total",
61  "Total Rainfall",
62  "total",
63  unit,
64  device_class=SensorDeviceClass.PRECIPITATION,
65  ),
66  ArwnSensor(
67  topic + "/rate",
68  "Rainfall Rate",
69  "rate",
70  unit,
71  device_class=SensorDeviceClass.PRECIPITATION,
72  ),
73  ]
74  if domain == "barometer":
75  return [
76  ArwnSensor(topic, "Barometer", "pressure", unit, "mdi:thermometer-lines")
77  ]
78  if domain == "wind":
79  return [
80  ArwnSensor(
81  topic + "/speed",
82  "Wind Speed",
83  "speed",
84  unit,
85  device_class=SensorDeviceClass.WIND_SPEED,
86  ),
87  ArwnSensor(
88  topic + "/gust",
89  "Wind Gust",
90  "gust",
91  unit,
92  device_class=SensorDeviceClass.WIND_SPEED,
93  ),
94  ArwnSensor(
95  topic + "/dir", "Wind Direction", "direction", DEGREE, "mdi:compass"
96  ),
97  ]
98  return None
99 
100 
101 def _slug(name: str) -> str:
102  return f"sensor.arwn_{slugify(name)}"
103 
104 
106  hass: HomeAssistant,
107  config: ConfigType,
108  async_add_entities: AddEntitiesCallback,
109  discovery_info: DiscoveryInfoType | None = None,
110 ) -> None:
111  """Set up the ARWN platform."""
112 
113  # Make sure MQTT integration is enabled and the client is available
114  if not await mqtt.async_wait_for_mqtt_client(hass):
115  _LOGGER.error("MQTT integration is not available")
116  return
117 
118  @callback
119  def async_sensor_event_received(msg: mqtt.ReceiveMessage) -> None:
120  """Process events as sensors.
121 
122  When a new event on our topic (arwn/#) is received we map it
123  into a known kind of sensor based on topic name. If we've
124  never seen this before, we keep this sensor around in a global
125  cache. If we have seen it before, we update the values of the
126  existing sensor. Either way, we push an ha state update at the
127  end for the new event we've seen.
128 
129  This lets us dynamically incorporate sensors without any
130  configuration on our side.
131  """
132  event = json_loads_object(msg.payload)
133  sensors = discover_sensors(msg.topic, event)
134  if not sensors:
135  return
136 
137  if (store := hass.data.get(DATA_ARWN)) is None:
138  store = hass.data[DATA_ARWN] = {}
139 
140  if "timestamp" in event:
141  del event["timestamp"]
142 
143  for sensor in sensors:
144  if sensor.name not in store:
145  sensor.hass = hass
146  sensor.set_event(event)
147  store[sensor.name] = sensor
148  _LOGGER.debug(
149  "Registering sensor %(name)s => %(event)s",
150  {"name": sensor.name, "event": event},
151  )
152  async_add_entities((sensor,), True)
153  else:
154  _LOGGER.debug(
155  "Recording sensor %(name)s => %(event)s",
156  {"name": sensor.name, "event": event},
157  )
158  store[sensor.name].set_event(event)
159 
160  await mqtt.async_subscribe(hass, TOPIC, async_sensor_event_received, 0)
161 
162 
164  """Representation of an ARWN sensor."""
165 
166  _attr_should_poll = False
167 
168  def __init__(
169  self,
170  topic: str,
171  name: str,
172  state_key: str,
173  units: str,
174  icon: str | None = None,
175  device_class: SensorDeviceClass | None = None,
176  ) -> None:
177  """Initialize the sensor."""
178  self.entity_identity_identity_id = _slug(name)
179  self._attr_name_attr_name = name
180  # This mqtt topic for the sensor which is its uid
181  self._attr_unique_id_attr_unique_id = topic
182  self._state_key_state_key = state_key
183  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = units
184  self._attr_icon_attr_icon = icon
185  self._attr_device_class_attr_device_class = device_class
186 
187  def set_event(self, event: dict[str, Any]) -> None:
188  """Update the sensor with the most recent event."""
189  ev: dict[str, Any] = {}
190  ev.update(event)
191  self._attr_extra_state_attributes_attr_extra_state_attributes = ev
192  self._attr_native_value_attr_native_value = ev.get(self._state_key_state_key, None)
193  self.async_write_ha_stateasync_write_ha_state()
None set_event(self, dict[str, Any] event)
Definition: sensor.py:187
None __init__(self, str topic, str name, str state_key, str units, str|None icon=None, SensorDeviceClass|None device_class=None)
Definition: sensor.py:176
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:110
list[ArwnSensor]|None discover_sensors(str topic, dict[str, Any] payload)
Definition: sensor.py:25
JsonObjectType json_loads_object(bytes|bytearray|memoryview|str obj)
Definition: json.py:54