Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for EnOcean sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 
8 from enocean.utils import combine_hex
9 import voluptuous as vol
10 
12  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
13  RestoreSensor,
14  SensorDeviceClass,
15  SensorEntityDescription,
16  SensorStateClass,
17 )
18 from homeassistant.const import (
19  CONF_DEVICE_CLASS,
20  CONF_ID,
21  CONF_NAME,
22  PERCENTAGE,
23  STATE_CLOSED,
24  STATE_OPEN,
25  UnitOfPower,
26  UnitOfTemperature,
27 )
28 from homeassistant.core import HomeAssistant
30 from homeassistant.helpers.entity_platform import AddEntitiesCallback
31 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
32 
33 from .entity import EnOceanEntity
34 
35 CONF_MAX_TEMP = "max_temp"
36 CONF_MIN_TEMP = "min_temp"
37 CONF_RANGE_FROM = "range_from"
38 CONF_RANGE_TO = "range_to"
39 
40 DEFAULT_NAME = "EnOcean sensor"
41 
42 SENSOR_TYPE_HUMIDITY = "humidity"
43 SENSOR_TYPE_POWER = "powersensor"
44 SENSOR_TYPE_TEMPERATURE = "temperature"
45 SENSOR_TYPE_WINDOWHANDLE = "windowhandle"
46 
47 
48 @dataclass(frozen=True, kw_only=True)
50  """Describes EnOcean sensor entity."""
51 
52  unique_id: Callable[[list[int]], str | None]
53 
54 
55 SENSOR_DESC_TEMPERATURE = EnOceanSensorEntityDescription(
56  key=SENSOR_TYPE_TEMPERATURE,
57  name="Temperature",
58  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
59  device_class=SensorDeviceClass.TEMPERATURE,
60  state_class=SensorStateClass.MEASUREMENT,
61  unique_id=lambda dev_id: f"{combine_hex(dev_id)}-{SENSOR_TYPE_TEMPERATURE}",
62 )
63 
64 SENSOR_DESC_HUMIDITY = EnOceanSensorEntityDescription(
65  key=SENSOR_TYPE_HUMIDITY,
66  name="Humidity",
67  native_unit_of_measurement=PERCENTAGE,
68  device_class=SensorDeviceClass.HUMIDITY,
69  state_class=SensorStateClass.MEASUREMENT,
70  unique_id=lambda dev_id: f"{combine_hex(dev_id)}-{SENSOR_TYPE_HUMIDITY}",
71 )
72 
73 SENSOR_DESC_POWER = EnOceanSensorEntityDescription(
74  key=SENSOR_TYPE_POWER,
75  name="Power",
76  native_unit_of_measurement=UnitOfPower.WATT,
77  device_class=SensorDeviceClass.POWER,
78  state_class=SensorStateClass.MEASUREMENT,
79  unique_id=lambda dev_id: f"{combine_hex(dev_id)}-{SENSOR_TYPE_POWER}",
80 )
81 
82 SENSOR_DESC_WINDOWHANDLE = EnOceanSensorEntityDescription(
83  key=SENSOR_TYPE_WINDOWHANDLE,
84  name="WindowHandle",
85  translation_key="window_handle",
86  unique_id=lambda dev_id: f"{combine_hex(dev_id)}-{SENSOR_TYPE_WINDOWHANDLE}",
87 )
88 
89 
90 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
91  {
92  vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]),
93  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
94  vol.Optional(CONF_DEVICE_CLASS, default=SENSOR_TYPE_POWER): cv.string,
95  vol.Optional(CONF_MAX_TEMP, default=40): vol.Coerce(int),
96  vol.Optional(CONF_MIN_TEMP, default=0): vol.Coerce(int),
97  vol.Optional(CONF_RANGE_FROM, default=255): cv.positive_int,
98  vol.Optional(CONF_RANGE_TO, default=0): cv.positive_int,
99  }
100 )
101 
102 
104  hass: HomeAssistant,
105  config: ConfigType,
106  add_entities: AddEntitiesCallback,
107  discovery_info: DiscoveryInfoType | None = None,
108 ) -> None:
109  """Set up an EnOcean sensor device."""
110  dev_id: list[int] = config[CONF_ID]
111  dev_name: str = config[CONF_NAME]
112  sensor_type: str = config[CONF_DEVICE_CLASS]
113 
114  entities: list[EnOceanSensor] = []
115  if sensor_type == SENSOR_TYPE_TEMPERATURE:
116  temp_min: int = config[CONF_MIN_TEMP]
117  temp_max: int = config[CONF_MAX_TEMP]
118  range_from: int = config[CONF_RANGE_FROM]
119  range_to: int = config[CONF_RANGE_TO]
120  entities = [
122  dev_id,
123  dev_name,
124  SENSOR_DESC_TEMPERATURE,
125  scale_min=temp_min,
126  scale_max=temp_max,
127  range_from=range_from,
128  range_to=range_to,
129  )
130  ]
131 
132  elif sensor_type == SENSOR_TYPE_HUMIDITY:
133  entities = [EnOceanHumiditySensor(dev_id, dev_name, SENSOR_DESC_HUMIDITY)]
134 
135  elif sensor_type == SENSOR_TYPE_POWER:
136  entities = [EnOceanPowerSensor(dev_id, dev_name, SENSOR_DESC_POWER)]
137 
138  elif sensor_type == SENSOR_TYPE_WINDOWHANDLE:
139  entities = [EnOceanWindowHandle(dev_id, dev_name, SENSOR_DESC_WINDOWHANDLE)]
140 
141  add_entities(entities)
142 
143 
145  """Representation of an EnOcean sensor device such as a power meter."""
146 
147  def __init__(
148  self,
149  dev_id: list[int],
150  dev_name: str,
151  description: EnOceanSensorEntityDescription,
152  ) -> None:
153  """Initialize the EnOcean sensor device."""
154  super().__init__(dev_id)
155  self.entity_descriptionentity_description = description
156  self._attr_name_attr_name = f"{description.name} {dev_name}"
157  self._attr_unique_id_attr_unique_id = description.unique_id(dev_id)
158 
159  async def async_added_to_hass(self) -> None:
160  """Call when entity about to be added to hass."""
161  # If not None, we got an initial value.
162  await super().async_added_to_hass()
163  if self._attr_native_value_attr_native_value is not None:
164  return
165 
166  if (sensor_data := await self.async_get_last_sensor_dataasync_get_last_sensor_data()) is not None:
167  self._attr_native_value_attr_native_value = sensor_data.native_value
168 
169  def value_changed(self, packet):
170  """Update the internal state of the sensor."""
171 
172 
173 class EnOceanPowerSensor(EnOceanSensor):
174  """Representation of an EnOcean power sensor.
175 
176  EEPs (EnOcean Equipment Profiles):
177  - A5-12-01 (Automated Meter Reading, Electricity)
178  """
179 
180  def value_changed(self, packet):
181  """Update the internal state of the sensor."""
182  if packet.rorg != 0xA5:
183  return
184  packet.parse_eep(0x12, 0x01)
185  if packet.parsed["DT"]["raw_value"] == 1:
186  # this packet reports the current value
187  raw_val = packet.parsed["MR"]["raw_value"]
188  divisor = packet.parsed["DIV"]["raw_value"]
189  self._attr_native_value_attr_native_value_attr_native_value = raw_val / (10**divisor)
190  self.schedule_update_ha_stateschedule_update_ha_state()
191 
192 
194  """Representation of an EnOcean temperature sensor device.
195 
196  EEPs (EnOcean Equipment Profiles):
197  - A5-02-01 to A5-02-1B All 8 Bit Temperature Sensors of A5-02
198  - A5-10-01 to A5-10-14 (Room Operating Panels)
199  - A5-04-01 (Temp. and Humidity Sensor, Range 0°C to +40°C and 0% to 100%)
200  - A5-04-02 (Temp. and Humidity Sensor, Range -20°C to +60°C and 0% to 100%)
201  - A5-10-10 (Temp. and Humidity Sensor and Set Point)
202  - A5-10-12 (Temp. and Humidity Sensor, Set Point and Occupancy Control)
203  - 10 Bit Temp. Sensors are not supported (A5-02-20, A5-02-30)
204 
205  For the following EEPs the scales must be set to "0 to 250":
206  - A5-04-01
207  - A5-04-02
208  - A5-10-10 to A5-10-14
209  """
210 
211  def __init__(
212  self,
213  dev_id: list[int],
214  dev_name: str,
215  description: EnOceanSensorEntityDescription,
216  *,
217  scale_min: int,
218  scale_max: int,
219  range_from: int,
220  range_to: int,
221  ) -> None:
222  """Initialize the EnOcean temperature sensor device."""
223  super().__init__(dev_id, dev_name, description)
224  self._scale_min_scale_min = scale_min
225  self._scale_max_scale_max = scale_max
226  self.range_fromrange_from = range_from
227  self.range_torange_to = range_to
228 
229  def value_changed(self, packet):
230  """Update the internal state of the sensor."""
231  if packet.data[0] != 0xA5:
232  return
233  temp_scale = self._scale_max_scale_max - self._scale_min_scale_min
234  temp_range = self.range_torange_to - self.range_fromrange_from
235  raw_val = packet.data[3]
236  temperature = temp_scale / temp_range * (raw_val - self.range_fromrange_from)
237  temperature += self._scale_min_scale_min
238  self._attr_native_value_attr_native_value_attr_native_value = round(temperature, 1)
239  self.schedule_update_ha_stateschedule_update_ha_state()
240 
241 
243  """Representation of an EnOcean humidity sensor device.
244 
245  EEPs (EnOcean Equipment Profiles):
246  - A5-04-01 (Temp. and Humidity Sensor, Range 0°C to +40°C and 0% to 100%)
247  - A5-04-02 (Temp. and Humidity Sensor, Range -20°C to +60°C and 0% to 100%)
248  - A5-10-10 to A5-10-14 (Room Operating Panels)
249  """
250 
251  def value_changed(self, packet):
252  """Update the internal state of the sensor."""
253  if packet.rorg != 0xA5:
254  return
255  humidity = packet.data[2] * 100 / 250
256  self._attr_native_value_attr_native_value_attr_native_value = round(humidity, 1)
257  self.schedule_update_ha_stateschedule_update_ha_state()
258 
259 
261  """Representation of an EnOcean window handle device.
262 
263  EEPs (EnOcean Equipment Profiles):
264  - F6-10-00 (Mechanical handle / Hoppe AG)
265  """
266 
267  def value_changed(self, packet):
268  """Update the internal state of the sensor."""
269  action = (packet.data[1] & 0x70) >> 4
270 
271  if action == 0x07:
272  self._attr_native_value_attr_native_value_attr_native_value = STATE_CLOSED
273  if action in (0x04, 0x06):
274  self._attr_native_value_attr_native_value_attr_native_value = STATE_OPEN
275  if action == 0x05:
276  self._attr_native_value_attr_native_value_attr_native_value = "tilt"
277 
278  self.schedule_update_ha_stateschedule_update_ha_state()
None __init__(self, list[int] dev_id, str dev_name, EnOceanSensorEntityDescription description)
Definition: sensor.py:152
None __init__(self, list[int] dev_id, str dev_name, EnOceanSensorEntityDescription description, *int scale_min, int scale_max, int range_from, int range_to)
Definition: sensor.py:221
SensorExtraStoredData|None async_get_last_sensor_data(self)
Definition: __init__.py:934
None schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1244
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:108
def add_entities(account, async_add_entities, tracked)
Definition: sensor.py:40