1 """Support for deCONZ sensors."""
3 from __future__
import annotations
5 from collections.abc
import Callable
6 from dataclasses
import dataclass
7 from datetime
import datetime
8 from typing
import Generic, TypeVar
10 from pydeconz.interfaces.sensors
import SensorResources
11 from pydeconz.models.event
import EventType
12 from pydeconz.models.sensor
import SensorBase
as PydeconzSensorBase
13 from pydeconz.models.sensor.air_purifier
import AirPurifier
14 from pydeconz.models.sensor.air_quality
import AirQuality
15 from pydeconz.models.sensor.carbon_dioxide
import CarbonDioxide
16 from pydeconz.models.sensor.consumption
import Consumption
17 from pydeconz.models.sensor.daylight
import DAYLIGHT_STATUS, Daylight
18 from pydeconz.models.sensor.formaldehyde
import Formaldehyde
19 from pydeconz.models.sensor.generic_status
import GenericStatus
20 from pydeconz.models.sensor.humidity
import Humidity
21 from pydeconz.models.sensor.light_level
import LightLevel
22 from pydeconz.models.sensor.moisture
import Moisture
23 from pydeconz.models.sensor.particulate_matter
import ParticulateMatter
24 from pydeconz.models.sensor.power
import Power
25 from pydeconz.models.sensor.pressure
import Pressure
26 from pydeconz.models.sensor.switch
import Switch
27 from pydeconz.models.sensor.temperature
import Temperature
28 from pydeconz.models.sensor.time
import Time
31 DOMAIN
as SENSOR_DOMAIN,
34 SensorEntityDescription,
41 CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
42 CONCENTRATION_PARTS_PER_BILLION,
43 CONCENTRATION_PARTS_PER_MILLION,
58 from .const
import ATTR_DARK, ATTR_ON
59 from .entity
import DeconzDevice
60 from .hub
import DeconzHub
62 PROVIDES_EXTRA_ATTRIBUTES = (
74 ATTR_CURRENT =
"current"
76 ATTR_DAYLIGHT =
"daylight"
77 ATTR_EVENT_ID =
"event_id"
101 @dataclass(frozen=True, kw_only=True)
103 """Class describing deCONZ binary sensor entities."""
105 instance_check: type[T] |
None =
None
106 name_suffix: str =
""
107 old_unique_id_suffix: str =
""
108 supported_fn: Callable[[T], bool]
110 value_fn: Callable[[T], datetime | StateType]
113 ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
114 DeconzSensorDescription[AirPurifier](
115 key=
"air_purifier_filter_run_time",
116 supported_fn=
lambda device:
True,
117 update_key=
"filterruntime",
118 name_suffix=
"Filter time",
119 value_fn=
lambda device: device.filter_run_time,
120 instance_check=AirPurifier,
121 device_class=SensorDeviceClass.DURATION,
122 entity_category=EntityCategory.DIAGNOSTIC,
123 native_unit_of_measurement=UnitOfTime.SECONDS,
124 suggested_unit_of_measurement=UnitOfTime.DAYS,
125 suggested_display_precision=1,
127 DeconzSensorDescription[AirQuality](
129 supported_fn=
lambda device: device.supports_air_quality,
130 update_key=
"airquality",
131 value_fn=
lambda device: device.air_quality,
132 instance_check=AirQuality,
134 DeconzSensorDescription[AirQuality](
135 key=
"air_quality_ppb",
136 supported_fn=
lambda device: device.air_quality_ppb
is not None,
137 update_key=
"airqualityppb",
138 value_fn=
lambda device: device.air_quality_ppb,
139 instance_check=AirQuality,
141 old_unique_id_suffix=
"ppb",
142 state_class=SensorStateClass.MEASUREMENT,
143 native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
145 DeconzSensorDescription[AirQuality](
146 key=
"air_quality_formaldehyde",
147 supported_fn=
lambda device: device.air_quality_formaldehyde
is not None,
148 update_key=
"airquality_formaldehyde_density",
149 value_fn=
lambda device: device.air_quality_formaldehyde,
150 instance_check=AirQuality,
152 device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
153 state_class=SensorStateClass.MEASUREMENT,
154 native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
156 DeconzSensorDescription[AirQuality](
157 key=
"air_quality_co2",
158 supported_fn=
lambda device: device.air_quality_co2
is not None,
159 update_key=
"airquality_co2_density",
160 value_fn=
lambda device: device.air_quality_co2,
161 instance_check=AirQuality,
163 device_class=SensorDeviceClass.CO2,
164 state_class=SensorStateClass.MEASUREMENT,
165 native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
167 DeconzSensorDescription[AirQuality](
168 key=
"air_quality_pm2_5",
169 supported_fn=
lambda device: device.pm_2_5
is not None,
171 value_fn=
lambda device: device.pm_2_5,
172 instance_check=AirQuality,
174 device_class=SensorDeviceClass.PM25,
175 state_class=SensorStateClass.MEASUREMENT,
176 native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
178 DeconzSensorDescription[CarbonDioxide](
179 key=
"carbon_dioxide",
180 supported_fn=
lambda device:
True,
181 update_key=
"measured_value",
182 value_fn=
lambda device: device.carbon_dioxide,
183 instance_check=CarbonDioxide,
184 device_class=SensorDeviceClass.CO2,
185 state_class=SensorStateClass.MEASUREMENT,
186 native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
188 DeconzSensorDescription[Consumption](
190 supported_fn=
lambda device: device.consumption
is not None,
191 update_key=
"consumption",
192 value_fn=
lambda device: device.scaled_consumption,
193 instance_check=Consumption,
194 device_class=SensorDeviceClass.ENERGY,
195 state_class=SensorStateClass.TOTAL_INCREASING,
196 native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
198 DeconzSensorDescription[Daylight](
199 key=
"daylight_status",
200 supported_fn=
lambda device:
True,
202 value_fn=
lambda device: DAYLIGHT_STATUS[device.daylight_status],
203 instance_check=Daylight,
204 icon=
"mdi:white-balance-sunny",
205 entity_registry_enabled_default=
False,
207 DeconzSensorDescription[Formaldehyde](
209 supported_fn=
lambda device:
True,
210 update_key=
"measured_value",
211 value_fn=
lambda device: device.formaldehyde,
212 instance_check=Formaldehyde,
213 device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
214 state_class=SensorStateClass.MEASUREMENT,
215 native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
217 DeconzSensorDescription[GenericStatus](
219 supported_fn=
lambda device: device.status
is not None,
221 value_fn=
lambda device: device.status,
222 instance_check=GenericStatus,
224 DeconzSensorDescription[Humidity](
226 supported_fn=
lambda device: device.humidity
is not None,
227 update_key=
"humidity",
228 value_fn=
lambda device: device.scaled_humidity,
229 instance_check=Humidity,
230 device_class=SensorDeviceClass.HUMIDITY,
231 state_class=SensorStateClass.MEASUREMENT,
232 native_unit_of_measurement=PERCENTAGE,
233 suggested_display_precision=1,
235 DeconzSensorDescription[LightLevel](
237 supported_fn=
lambda device: device.light_level
is not None,
238 update_key=
"lightlevel",
239 value_fn=
lambda device: device.scaled_light_level,
240 instance_check=LightLevel,
241 device_class=SensorDeviceClass.ILLUMINANCE,
242 state_class=SensorStateClass.MEASUREMENT,
243 native_unit_of_measurement=LIGHT_LUX,
245 DeconzSensorDescription[Moisture](
247 supported_fn=
lambda device: device.moisture
is not None,
248 update_key=
"moisture",
249 value_fn=
lambda device: device.scaled_moisture,
250 instance_check=Moisture,
251 device_class=SensorDeviceClass.MOISTURE,
252 state_class=SensorStateClass.MEASUREMENT,
253 native_unit_of_measurement=PERCENTAGE,
254 suggested_display_precision=1,
256 DeconzSensorDescription[ParticulateMatter](
257 key=
"particulate_matter_pm2_5",
258 supported_fn=
lambda device: device.measured_value
is not None,
259 update_key=
"measured_value",
260 value_fn=
lambda device: device.measured_value,
261 instance_check=ParticulateMatter,
263 device_class=SensorDeviceClass.PM25,
264 state_class=SensorStateClass.MEASUREMENT,
265 native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
267 DeconzSensorDescription[Power](
269 supported_fn=
lambda device: device.power
is not None,
271 value_fn=
lambda device: device.power,
272 instance_check=Power,
273 device_class=SensorDeviceClass.POWER,
274 state_class=SensorStateClass.MEASUREMENT,
275 native_unit_of_measurement=UnitOfPower.WATT,
277 DeconzSensorDescription[Pressure](
279 supported_fn=
lambda device: device.pressure
is not None,
280 update_key=
"pressure",
281 value_fn=
lambda device: device.pressure,
282 instance_check=Pressure,
283 device_class=SensorDeviceClass.PRESSURE,
284 state_class=SensorStateClass.MEASUREMENT,
285 native_unit_of_measurement=UnitOfPressure.HPA,
287 DeconzSensorDescription[Temperature](
289 supported_fn=
lambda device: device.temperature
is not None,
290 update_key=
"temperature",
291 value_fn=
lambda device: device.scaled_temperature,
292 instance_check=Temperature,
293 device_class=SensorDeviceClass.TEMPERATURE,
294 state_class=SensorStateClass.MEASUREMENT,
295 native_unit_of_measurement=UnitOfTemperature.CELSIUS,
296 suggested_display_precision=1,
298 DeconzSensorDescription[Time](
300 supported_fn=
lambda device: device.last_set
is not None,
301 update_key=
"lastset",
302 value_fn=
lambda device: dt_util.parse_datetime(device.last_set),
304 device_class=SensorDeviceClass.TIMESTAMP,
306 DeconzSensorDescription[SensorResources](
308 supported_fn=
lambda device: device.battery
is not None,
309 update_key=
"battery",
310 value_fn=
lambda device: device.battery,
311 name_suffix=
"Battery",
312 old_unique_id_suffix=
"battery",
313 device_class=SensorDeviceClass.BATTERY,
314 state_class=SensorStateClass.MEASUREMENT,
315 native_unit_of_measurement=PERCENTAGE,
316 entity_category=EntityCategory.DIAGNOSTIC,
318 DeconzSensorDescription[SensorResources](
319 key=
"internal_temperature",
320 supported_fn=
lambda device: device.internal_temperature
is not None,
321 update_key=
"temperature",
322 value_fn=
lambda device: device.internal_temperature,
323 name_suffix=
"Temperature",
324 old_unique_id_suffix=
"temperature",
325 device_class=SensorDeviceClass.TEMPERATURE,
326 state_class=SensorStateClass.MEASUREMENT,
327 native_unit_of_measurement=UnitOfTemperature.CELSIUS,
334 config_entry: ConfigEntry,
335 async_add_entities: AddEntitiesCallback,
337 """Set up the deCONZ sensors."""
338 hub = DeconzHub.get_hub(hass, config_entry)
339 hub.entities[SENSOR_DOMAIN] = set()
341 known_device_entities: dict[str, set[str]] = {
342 description.key: set()
343 for description
in ENTITY_DESCRIPTIONS
344 if description.instance_check
is None
348 def async_add_sensor(_: EventType, sensor_id: str) ->
None:
349 """Add sensor from deCONZ."""
350 sensor = hub.api.sensors[sensor_id]
351 entities: list[DeconzSensor] = []
353 for description
in ENTITY_DESCRIPTIONS:
354 if description.instance_check
and not isinstance(
355 sensor, description.instance_check
359 no_sensor_data =
False
360 if not description.supported_fn(sensor):
361 no_sensor_data =
True
363 if description.instance_check
is None:
365 sensor.type.startswith(
"CLIP")
366 or (no_sensor_data
and description.key !=
"battery")
368 (unique_id := sensor.unique_id.rpartition(
"-")[0])
369 in known_device_entities[description.key]
373 known_device_entities[description.key].
add(unique_id)
374 if no_sensor_data
and description.key ==
"battery":
376 sensor_id, hub, description, async_add_entities
387 hub.register_platform_add_device_callback(
394 """Representation of a deCONZ sensor."""
397 entity_description: DeconzSensorDescription
401 device: SensorResources,
403 description: DeconzSensorDescription,
405 """Initialize deCONZ sensor."""
409 if description.name_suffix:
415 and self._update_keys
is not None
417 self._update_keys.
update({
"on",
"state"})
421 """Return the state of the sensor."""
426 """Return the state attributes of the sensor."""
427 attr: dict[str, bool | float | int | str |
None] = {}
432 if self.
_device_device.on
is not None:
433 attr[ATTR_ON] = self.
_device_device.on
435 if self.
_device_device.internal_temperature
is not None:
436 attr[ATTR_TEMPERATURE] = self.
_device_device.internal_temperature
438 if isinstance(self.
_device_device, Consumption):
439 attr[ATTR_POWER] = self.
_device_device.power
441 elif isinstance(self.
_device_device, Daylight):
442 attr[ATTR_DAYLIGHT] = self.
_device_device.daylight
444 elif isinstance(self.
_device_device, LightLevel):
445 if self.
_device_device.dark
is not None:
446 attr[ATTR_DARK] = self.
_device_device.dark
448 if self.
_device_device.daylight
is not None:
449 attr[ATTR_DAYLIGHT] = self.
_device_device.daylight
451 elif isinstance(self.
_device_device, Power):
452 attr[ATTR_CURRENT] = self.
_device_device.current
453 attr[ATTR_VOLTAGE] = self.
_device_device.voltage
455 elif isinstance(self.
_device_device, Switch):
456 for event
in self.hub.events:
458 attr[ATTR_EVENT_ID] = event.event_id
464 """Track sensors without a battery state and add entity when battery state exist."""
470 description: DeconzSensorDescription,
471 async_add_entities: AddEntitiesCallback,
473 """Set up tracker."""
474 self.
sensorsensor = hub.api.sensors[sensor_id]
482 """Update the device's state."""
None __init__(self, str sensor_id, DeconzHub hub, DeconzSensorDescription description, AddEntitiesCallback async_add_entities)
None async_update_callback(self)
None __init__(self, SensorResources device, DeconzHub hub, DeconzSensorDescription description)
StateType|datetime native_value(self)
dict[str, bool|float|int|str|None] extra_state_attributes(self)
bool add(self, _T matcher)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
IssData update(pyiss.ISS iss)
Callable[[], None] subscribe(HomeAssistant hass, str topic, MessageCallbackType msg_callback, int qos=DEFAULT_QOS, str encoding="utf-8")