Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Homekit sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from enum import IntEnum
8 
9 from aiohomekit.model import Accessory, Transport
10 from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes
11 from aiohomekit.model.characteristics.const import (
12  CurrentAirPurifierStateValues,
13  ThreadNodeCapabilities,
14  ThreadStatus,
15 )
16 from aiohomekit.model.services import Service, ServicesTypes
17 
19  async_ble_device_from_address,
20  async_last_service_info,
21 )
23  SensorDeviceClass,
24  SensorEntity,
25  SensorEntityDescription,
26  SensorStateClass,
27 )
28 from homeassistant.config_entries import ConfigEntry
29 from homeassistant.const import (
30  CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
31  CONCENTRATION_PARTS_PER_MILLION,
32  LIGHT_LUX,
33  PERCENTAGE,
34  SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
35  EntityCategory,
36  Platform,
37  UnitOfElectricCurrent,
38  UnitOfElectricPotential,
39  UnitOfEnergy,
40  UnitOfPower,
41  UnitOfPressure,
42  UnitOfSoundPressure,
43  UnitOfTemperature,
44 )
45 from homeassistant.core import HomeAssistant, callback
46 from homeassistant.helpers.entity_platform import AddEntitiesCallback
47 from homeassistant.helpers.typing import ConfigType
48 
49 from . import KNOWN_DEVICES
50 from .connection import HKDevice
51 from .entity import CharacteristicEntity, HomeKitEntity
52 from .utils import folded_name
53 
54 
55 @dataclass(frozen=True)
57  """Describes Homekit sensor."""
58 
59  probe: Callable[[Characteristic], bool] | None = None
60  format: Callable[[Characteristic], str] | None = None
61  enum: dict[IntEnum, str] | None = None
62 
63 
64 def thread_node_capability_to_str(char: Characteristic) -> str:
65  """Return the thread device type as a string.
66 
67  The underlying value is a bitmask, but we want to turn that to
68  a human readable string. Some devices will have multiple capabilities.
69  For example, an NL55 is SLEEPY | MINIMAL. In that case we return the
70  "best" capability.
71 
72  https://openthread.io/guides/thread-primer/node-roles-and-types
73  """
74 
75  val = ThreadNodeCapabilities(char.value)
76 
77  if val & ThreadNodeCapabilities.BORDER_ROUTER_CAPABLE:
78  # can act as a bridge between thread network and e.g. WiFi
79  return "border_router_capable"
80 
81  if val & ThreadNodeCapabilities.ROUTER_ELIGIBLE:
82  # radio always on, can be a router
83  return "router_eligible"
84 
85  if val & ThreadNodeCapabilities.FULL:
86  # radio always on, but can't be a router
87  return "full"
88 
89  if val & ThreadNodeCapabilities.MINIMAL:
90  # transceiver always on, does not need to poll for messages from its parent
91  return "minimal"
92 
93  if val & ThreadNodeCapabilities.SLEEPY:
94  # normally disabled, wakes on occasion to poll for messages from its parent
95  return "sleepy"
96 
97  # Device has no known thread capabilities
98  return "none"
99 
100 
101 def thread_status_to_str(char: Characteristic) -> str:
102  """Return the thread status as a string.
103 
104  The underlying value is a bitmask, but we want to turn that to
105  a human readable string. So we check the flags in order. E.g. BORDER_ROUTER implies
106  ROUTER, so its more important to show that value.
107  """
108 
109  val = ThreadStatus(char.value)
110 
111  if val & ThreadStatus.BORDER_ROUTER:
112  # Device has joined the Thread network and is participating
113  # in routing between mesh nodes.
114  # It's also the border router - bridging the thread network
115  # to WiFI/Ethernet/etc
116  return "border_router"
117 
118  if val & ThreadStatus.LEADER:
119  # Device has joined the Thread network and is participating
120  # in routing between mesh nodes.
121  # It's also the leader. There's only one leader and it manages
122  # which nodes are routers.
123  return "leader"
124 
125  if val & ThreadStatus.ROUTER:
126  # Device has joined the Thread network and is participating
127  # in routing between mesh nodes.
128  return "router"
129 
130  if val & ThreadStatus.CHILD:
131  # Device has joined the Thread network as a child
132  # It's not participating in routing between mesh nodes
133  return "child"
134 
135  if val & ThreadStatus.JOINING:
136  # Device is currently joining its Thread network
137  return "joining"
138 
139  if val & ThreadStatus.DETACHED:
140  # Device is currently unable to reach its Thread network
141  return "detached"
142 
143  # Must be ThreadStatus.DISABLED
144  # Device is not currently connected to Thread and will not try to.
145  return "disabled"
146 
147 
148 SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
149  CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_WATT: HomeKitSensorEntityDescription(
150  key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_WATT,
151  name="Power",
152  device_class=SensorDeviceClass.POWER,
153  state_class=SensorStateClass.MEASUREMENT,
154  native_unit_of_measurement=UnitOfPower.WATT,
155  ),
156  CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS: HomeKitSensorEntityDescription(
157  key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS,
158  name="Current",
159  device_class=SensorDeviceClass.CURRENT,
160  state_class=SensorStateClass.MEASUREMENT,
161  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
162  ),
163  CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS_20: HomeKitSensorEntityDescription(
164  key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS_20,
165  name="Current",
166  device_class=SensorDeviceClass.CURRENT,
167  state_class=SensorStateClass.MEASUREMENT,
168  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
169  ),
170  CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_KW_HOUR: HomeKitSensorEntityDescription(
171  key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_KW_HOUR,
172  name="Energy kWh",
173  device_class=SensorDeviceClass.ENERGY,
174  state_class=SensorStateClass.TOTAL_INCREASING,
175  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
176  ),
177  CharacteristicsTypes.VENDOR_EVE_ENERGY_WATT: HomeKitSensorEntityDescription(
178  key=CharacteristicsTypes.VENDOR_EVE_ENERGY_WATT,
179  name="Power",
180  device_class=SensorDeviceClass.POWER,
181  state_class=SensorStateClass.MEASUREMENT,
182  native_unit_of_measurement=UnitOfPower.WATT,
183  ),
184  CharacteristicsTypes.VENDOR_EVE_ENERGY_KW_HOUR: HomeKitSensorEntityDescription(
185  key=CharacteristicsTypes.VENDOR_EVE_ENERGY_KW_HOUR,
186  name="Energy kWh",
187  device_class=SensorDeviceClass.ENERGY,
188  state_class=SensorStateClass.TOTAL_INCREASING,
189  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
190  ),
191  CharacteristicsTypes.VENDOR_EVE_ENERGY_VOLTAGE: HomeKitSensorEntityDescription(
192  key=CharacteristicsTypes.VENDOR_EVE_ENERGY_VOLTAGE,
193  name="Volts",
194  device_class=SensorDeviceClass.VOLTAGE,
195  state_class=SensorStateClass.MEASUREMENT,
196  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
197  ),
198  CharacteristicsTypes.VENDOR_EVE_ENERGY_AMPERE: HomeKitSensorEntityDescription(
199  key=CharacteristicsTypes.VENDOR_EVE_ENERGY_AMPERE,
200  name="Amps",
201  device_class=SensorDeviceClass.CURRENT,
202  state_class=SensorStateClass.MEASUREMENT,
203  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
204  ),
205  CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY: HomeKitSensorEntityDescription(
206  key=CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY,
207  name="Power",
208  device_class=SensorDeviceClass.POWER,
209  state_class=SensorStateClass.MEASUREMENT,
210  native_unit_of_measurement=UnitOfPower.WATT,
211  ),
212  CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY_2: HomeKitSensorEntityDescription(
213  key=CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY_2,
214  name="Power",
215  device_class=SensorDeviceClass.POWER,
216  state_class=SensorStateClass.MEASUREMENT,
217  native_unit_of_measurement=UnitOfPower.WATT,
218  ),
219  CharacteristicsTypes.VENDOR_EVE_DEGREE_AIR_PRESSURE: HomeKitSensorEntityDescription(
220  key=CharacteristicsTypes.VENDOR_EVE_DEGREE_AIR_PRESSURE,
221  name="Air Pressure",
222  device_class=SensorDeviceClass.PRESSURE,
223  state_class=SensorStateClass.MEASUREMENT,
224  native_unit_of_measurement=UnitOfPressure.HPA,
225  ),
226  CharacteristicsTypes.VENDOR_VOCOLINC_OUTLET_ENERGY: HomeKitSensorEntityDescription(
227  key=CharacteristicsTypes.VENDOR_VOCOLINC_OUTLET_ENERGY,
228  name="Power",
229  device_class=SensorDeviceClass.POWER,
230  state_class=SensorStateClass.MEASUREMENT,
231  native_unit_of_measurement=UnitOfPower.WATT,
232  ),
233  CharacteristicsTypes.TEMPERATURE_CURRENT: HomeKitSensorEntityDescription(
234  key=CharacteristicsTypes.TEMPERATURE_CURRENT,
235  name="Current Temperature",
236  device_class=SensorDeviceClass.TEMPERATURE,
237  state_class=SensorStateClass.MEASUREMENT,
238  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
239  # This sensor is only for temperature characteristics that are not part
240  # of a temperature sensor service.
241  probe=(lambda char: char.service.type != ServicesTypes.TEMPERATURE_SENSOR),
242  ),
243  CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT: HomeKitSensorEntityDescription(
244  key=CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT,
245  name="Current Humidity",
246  device_class=SensorDeviceClass.HUMIDITY,
247  state_class=SensorStateClass.MEASUREMENT,
248  native_unit_of_measurement=PERCENTAGE,
249  # This sensor is only for humidity characteristics that are not part
250  # of a humidity sensor service.
251  probe=(lambda char: char.service.type != ServicesTypes.HUMIDITY_SENSOR),
252  ),
253  CharacteristicsTypes.AIR_QUALITY: HomeKitSensorEntityDescription(
254  key=CharacteristicsTypes.AIR_QUALITY,
255  name="Air Quality",
256  device_class=SensorDeviceClass.AQI,
257  state_class=SensorStateClass.MEASUREMENT,
258  ),
259  CharacteristicsTypes.DENSITY_PM25: HomeKitSensorEntityDescription(
260  key=CharacteristicsTypes.DENSITY_PM25,
261  name="PM2.5 Density",
262  device_class=SensorDeviceClass.PM25,
263  state_class=SensorStateClass.MEASUREMENT,
264  native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
265  ),
266  CharacteristicsTypes.DENSITY_PM10: HomeKitSensorEntityDescription(
267  key=CharacteristicsTypes.DENSITY_PM10,
268  name="PM10 Density",
269  device_class=SensorDeviceClass.PM10,
270  state_class=SensorStateClass.MEASUREMENT,
271  native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
272  ),
273  CharacteristicsTypes.DENSITY_OZONE: HomeKitSensorEntityDescription(
274  key=CharacteristicsTypes.DENSITY_OZONE,
275  name="Ozone Density",
276  device_class=SensorDeviceClass.OZONE,
277  state_class=SensorStateClass.MEASUREMENT,
278  native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
279  ),
280  CharacteristicsTypes.DENSITY_NO2: HomeKitSensorEntityDescription(
281  key=CharacteristicsTypes.DENSITY_NO2,
282  name="Nitrogen Dioxide Density",
283  device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
284  state_class=SensorStateClass.MEASUREMENT,
285  native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
286  ),
287  CharacteristicsTypes.DENSITY_SO2: HomeKitSensorEntityDescription(
288  key=CharacteristicsTypes.DENSITY_SO2,
289  name="Sulphur Dioxide Density",
290  device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
291  state_class=SensorStateClass.MEASUREMENT,
292  native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
293  ),
294  CharacteristicsTypes.DENSITY_VOC: HomeKitSensorEntityDescription(
295  key=CharacteristicsTypes.DENSITY_VOC,
296  name="Volatile Organic Compound Density",
297  device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
298  state_class=SensorStateClass.MEASUREMENT,
299  native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
300  ),
301  CharacteristicsTypes.THREAD_NODE_CAPABILITIES: HomeKitSensorEntityDescription(
302  key=CharacteristicsTypes.THREAD_NODE_CAPABILITIES,
303  name="Thread Capabilities",
304  entity_category=EntityCategory.DIAGNOSTIC,
305  format=thread_node_capability_to_str,
306  device_class=SensorDeviceClass.ENUM,
307  options=[
308  "border_router_capable",
309  "full",
310  "minimal",
311  "none",
312  "router_eligible",
313  "sleepy",
314  ],
315  translation_key="thread_node_capabilities",
316  ),
317  CharacteristicsTypes.THREAD_STATUS: HomeKitSensorEntityDescription(
318  key=CharacteristicsTypes.THREAD_STATUS,
319  name="Thread Status",
320  entity_category=EntityCategory.DIAGNOSTIC,
321  format=thread_status_to_str,
322  device_class=SensorDeviceClass.ENUM,
323  options=[
324  "border_router",
325  "child",
326  "detached",
327  "disabled",
328  "joining",
329  "leader",
330  "router",
331  ],
332  translation_key="thread_status",
333  ),
334  CharacteristicsTypes.AIR_PURIFIER_STATE_CURRENT: HomeKitSensorEntityDescription(
335  key=CharacteristicsTypes.AIR_PURIFIER_STATE_CURRENT,
336  name="Air Purifier Status",
337  entity_category=EntityCategory.DIAGNOSTIC,
338  device_class=SensorDeviceClass.ENUM,
339  enum={
340  CurrentAirPurifierStateValues.INACTIVE: "inactive",
341  CurrentAirPurifierStateValues.IDLE: "idle",
342  CurrentAirPurifierStateValues.ACTIVE: "purifying",
343  },
344  translation_key="air_purifier_state_current",
345  ),
346  CharacteristicsTypes.VENDOR_NETATMO_NOISE: HomeKitSensorEntityDescription(
347  key=CharacteristicsTypes.VENDOR_NETATMO_NOISE,
348  name="Noise",
349  state_class=SensorStateClass.MEASUREMENT,
350  native_unit_of_measurement=UnitOfSoundPressure.DECIBEL,
351  device_class=SensorDeviceClass.SOUND_PRESSURE,
352  ),
353  CharacteristicsTypes.FILTER_LIFE_LEVEL: HomeKitSensorEntityDescription(
354  key=CharacteristicsTypes.FILTER_LIFE_LEVEL,
355  name="Filter lifetime",
356  state_class=SensorStateClass.MEASUREMENT,
357  native_unit_of_measurement=PERCENTAGE,
358  ),
359  CharacteristicsTypes.VENDOR_EVE_THERMO_VALVE_POSITION: HomeKitSensorEntityDescription(
360  key=CharacteristicsTypes.VENDOR_EVE_THERMO_VALVE_POSITION,
361  name="Valve position",
362  translation_key="valve_position",
363  entity_category=EntityCategory.DIAGNOSTIC,
364  state_class=SensorStateClass.MEASUREMENT,
365  native_unit_of_measurement=PERCENTAGE,
366  ),
367 }
368 
369 
371  """Representation of a HomeKit sensor."""
372 
373  _attr_state_class = SensorStateClass.MEASUREMENT
374 
375  @property
376  def name(self) -> str | None:
377  """Return the name of the device."""
378  full_name = super().name
379  default_name = self.default_namedefault_name
380  if (
381  default_name
382  and full_name
383  and folded_name(default_name) not in folded_name(full_name)
384  ):
385  return f"{full_name} {default_name}"
386  return full_name
387 
388 
390  """Representation of a Homekit humidity sensor."""
391 
392  _attr_device_class = SensorDeviceClass.HUMIDITY
393  _attr_native_unit_of_measurement = PERCENTAGE
394 
395  def get_characteristic_types(self) -> list[str]:
396  """Define the homekit characteristics the entity is tracking."""
397  return [CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT]
398 
399  @property
400  def default_name(self) -> str:
401  """Return the default name of the device."""
402  return "Humidity"
403 
404  @property
405  def native_value(self) -> float:
406  """Return the current humidity."""
407  return self.serviceservice.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT)
408 
409 
411  """Representation of a Homekit temperature sensor."""
412 
413  _attr_device_class = SensorDeviceClass.TEMPERATURE
414  _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
415 
416  def get_characteristic_types(self) -> list[str]:
417  """Define the homekit characteristics the entity is tracking."""
418  return [CharacteristicsTypes.TEMPERATURE_CURRENT]
419 
420  @property
421  def default_name(self) -> str:
422  """Return the default name of the device."""
423  return "Temperature"
424 
425  @property
426  def native_value(self) -> float:
427  """Return the current temperature in Celsius."""
428  return self.serviceservice.value(CharacteristicsTypes.TEMPERATURE_CURRENT)
429 
430 
432  """Representation of a Homekit light level sensor."""
433 
434  _attr_device_class = SensorDeviceClass.ILLUMINANCE
435  _attr_native_unit_of_measurement = LIGHT_LUX
436 
437  def get_characteristic_types(self) -> list[str]:
438  """Define the homekit characteristics the entity is tracking."""
439  return [CharacteristicsTypes.LIGHT_LEVEL_CURRENT]
440 
441  @property
442  def default_name(self) -> str:
443  """Return the default name of the device."""
444  return "Light Level"
445 
446  @property
447  def native_value(self) -> int:
448  """Return the current light level in lux."""
449  return self.serviceservice.value(CharacteristicsTypes.LIGHT_LEVEL_CURRENT)
450 
451 
453  """Representation of a Homekit Carbon Dioxide sensor."""
454 
455  _attr_device_class = SensorDeviceClass.CO2
456  _attr_native_unit_of_measurement = CONCENTRATION_PARTS_PER_MILLION
457 
458  def get_characteristic_types(self) -> list[str]:
459  """Define the homekit characteristics the entity is tracking."""
460  return [CharacteristicsTypes.CARBON_DIOXIDE_LEVEL]
461 
462  @property
463  def default_name(self) -> str:
464  """Return the default name of the device."""
465  return "Carbon Dioxide"
466 
467  @property
468  def native_value(self) -> int:
469  """Return the current CO2 level in ppm."""
470  return self.serviceservice.value(CharacteristicsTypes.CARBON_DIOXIDE_LEVEL)
471 
472 
474  """Representation of a Homekit battery sensor."""
475 
476  _attr_device_class = SensorDeviceClass.BATTERY
477  _attr_native_unit_of_measurement = PERCENTAGE
478  _attr_entity_category = EntityCategory.DIAGNOSTIC
479 
480  def get_characteristic_types(self) -> list[str]:
481  """Define the homekit characteristics the entity is tracking."""
482  return [
483  CharacteristicsTypes.BATTERY_LEVEL,
484  CharacteristicsTypes.STATUS_LO_BATT,
485  CharacteristicsTypes.CHARGING_STATE,
486  ]
487 
488  @property
489  def default_name(self) -> str:
490  """Return the default name of the device."""
491  return "Battery"
492 
493  @property
494  def icon(self) -> str:
495  """Return the sensor icon."""
496  native_value = self.native_valuenative_valuenative_value
497  if not self.availableavailableavailable or native_value is None:
498  return "mdi:battery-unknown"
499 
500  # This is similar to the logic in helpers.icon, but we have delegated the
501  # decision about what mdi:battery-alert is to the device.
502  icon = "mdi:battery"
503  is_charging = self.is_chargingis_charging
504  if is_charging and native_value > 10:
505  percentage = int(round(native_value / 20 - 0.01)) * 20
506  icon += f"-charging-{percentage}"
507  elif is_charging:
508  icon += "-outline"
509  elif self.is_low_batteryis_low_battery:
510  icon += "-alert"
511  elif native_value < 95:
512  percentage = max(int(round(native_value / 10 - 0.01)) * 10, 10)
513  icon += f"-{percentage}"
514 
515  return icon
516 
517  @property
518  def is_low_battery(self) -> bool:
519  """Return true if battery level is low."""
520  return self.serviceservice.value(CharacteristicsTypes.STATUS_LO_BATT) == 1
521 
522  @property
523  def is_charging(self) -> bool:
524  """Return true if currently charging."""
525  # 0 = not charging
526  # 1 = charging
527  # 2 = not chargeable
528  return self.serviceservice.value(CharacteristicsTypes.CHARGING_STATE) == 1
529 
530  @property
531  def native_value(self) -> int:
532  """Return the current battery level percentage."""
533  return self.serviceservice.value(CharacteristicsTypes.BATTERY_LEVEL)
534 
535 
537  """A simple sensor for a single characteristic.
538 
539  This may be an additional secondary entity that is part of another service. An
540  example is a switch that has an energy sensor.
541 
542  These *have* to have a different unique_id to the normal sensors as there could
543  be multiple entities per HomeKit service (this was not previously the case).
544  """
545 
546  entity_description: HomeKitSensorEntityDescription
547 
548  def __init__(
549  self,
550  conn: HKDevice,
551  info: ConfigType,
552  char: Characteristic,
553  description: HomeKitSensorEntityDescription,
554  ) -> None:
555  """Initialise a secondary HomeKit characteristic sensor."""
556  self.entity_descriptionentity_description = description
557  if self.entity_descriptionentity_description.enum:
558  self._attr_options_attr_options = list(self.entity_descriptionentity_description.enum.values())
559  super().__init__(conn, info, char)
560 
561  def get_characteristic_types(self) -> list[str]:
562  """Define the homekit characteristics the entity is tracking."""
563  return [self._char_char.type]
564 
565  @property
566  def name(self) -> str:
567  """Return the name of the device if any."""
568  if name := self.accessoryaccessory.name:
569  return f"{name} {self.entity_description.name}"
570  return f"{self.entity_description.name}"
571 
572  @property
573  def native_value(self) -> str | int | float:
574  """Return the current sensor value."""
575  if self.entity_descriptionentity_description.enum:
576  return self.entity_descriptionentity_description.enum[self._char_char.value]
577  if self.entity_descriptionentity_description.format:
578  return self.entity_descriptionentity_description.format(self._char_char)
579  return self._char_char.value
580 
581 
582 ENTITY_TYPES = {
583  ServicesTypes.HUMIDITY_SENSOR: HomeKitHumiditySensor,
584  ServicesTypes.TEMPERATURE_SENSOR: HomeKitTemperatureSensor,
585  ServicesTypes.LIGHT_SENSOR: HomeKitLightSensor,
586  ServicesTypes.CARBON_DIOXIDE_SENSOR: HomeKitCarbonDioxideSensor,
587  ServicesTypes.BATTERY_SERVICE: HomeKitBatterySensor,
588 }
589 
590 # Only create the entity if it has the required characteristic
591 REQUIRED_CHAR_BY_TYPE = {
592  ServicesTypes.BATTERY_SERVICE: CharacteristicsTypes.BATTERY_LEVEL,
593 }
594 
595 
597  """HomeKit Controller RSSI sensor."""
598 
599  _attr_device_class = SensorDeviceClass.SIGNAL_STRENGTH
600  _attr_entity_category = EntityCategory.DIAGNOSTIC
601  _attr_entity_registry_enabled_default = False
602  _attr_has_entity_name = True
603  _attr_native_unit_of_measurement = SIGNAL_STRENGTH_DECIBELS_MILLIWATT
604  _attr_should_poll = False
605 
606  def __init__(self, accessory: HKDevice, devinfo: ConfigType) -> None:
607  """Initialise a HomeKit Controller RSSI sensor."""
608  super().__init__(accessory, devinfo)
609  self._attr_unique_id_attr_unique_id_attr_unique_id = f"{accessory.unique_id}_rssi"
610 
611  def get_characteristic_types(self) -> list[str]:
612  """Define the homekit characteristics the entity cares about."""
613  return []
614 
615  @property
616  def available(self) -> bool:
617  """Return if the bluetooth device is available."""
618  address = self._accessory_accessory.pairing_data["AccessoryAddress"]
619  return async_ble_device_from_address(self.hasshass, address) is not None
620 
621  @property
622  def name(self) -> str:
623  """Return the name of the sensor."""
624  return "Signal strength"
625 
626  @property
627  def old_unique_id(self) -> str:
628  """Return the old ID of this device."""
629  serial = self.accessory_infoaccessory_info.value(CharacteristicsTypes.SERIAL_NUMBER)
630  return f"homekit-{serial}-rssi"
631 
632  @property
633  def native_value(self) -> int | None:
634  """Return the current rssi value."""
635  address = self._accessory_accessory.pairing_data["AccessoryAddress"]
636  last_service_info = async_last_service_info(self.hasshass, address)
637  return last_service_info.rssi if last_service_info else None
638 
639 
641  hass: HomeAssistant,
642  config_entry: ConfigEntry,
643  async_add_entities: AddEntitiesCallback,
644 ) -> None:
645  """Set up Homekit sensors."""
646  hkid = config_entry.data["AccessoryPairingID"]
647  conn: HKDevice = hass.data[KNOWN_DEVICES][hkid]
648 
649  @callback
650  def async_add_service(service: Service) -> bool:
651  if not (entity_class := ENTITY_TYPES.get(service.type)):
652  return False
653  if (
654  required_char := REQUIRED_CHAR_BY_TYPE.get(service.type)
655  ) and not service.has(required_char):
656  return False
657  info = {"aid": service.accessory.aid, "iid": service.iid}
658  entity: HomeKitSensor = entity_class(conn, info)
659  conn.async_migrate_unique_id(
660  entity.old_unique_id, entity.unique_id, Platform.SENSOR
661  )
662  async_add_entities([entity])
663  return True
664 
665  conn.add_listener(async_add_service)
666 
667  @callback
668  def async_add_characteristic(char: Characteristic) -> bool:
669  if not (description := SIMPLE_SENSOR.get(char.type)):
670  return False
671  if description.probe and not description.probe(char):
672  return False
673  info = {"aid": char.service.accessory.aid, "iid": char.service.iid}
674  entity = SimpleSensor(conn, info, char, description)
675  conn.async_migrate_unique_id(
676  entity.old_unique_id, entity.unique_id, Platform.SENSOR
677  )
678  async_add_entities([entity])
679 
680  return True
681 
682  conn.add_char_factory(async_add_characteristic)
683 
684  @callback
685  def async_add_accessory(accessory: Accessory) -> bool:
686  if conn.pairing.transport != Transport.BLE:
687  return False
688 
689  accessory_info = accessory.services.first(
690  service_type=ServicesTypes.ACCESSORY_INFORMATION
691  )
692  assert accessory_info
693  info = {"aid": accessory.aid, "iid": accessory_info.iid}
694  entity = RSSISensor(conn, info)
695  conn.async_migrate_unique_id(
696  entity.old_unique_id, entity.unique_id, Platform.SENSOR
697  )
698  async_add_entities([entity])
699  return True
700 
701  conn.add_accessory_factory(async_add_accessory)
None __init__(self, HKDevice accessory, ConfigType devinfo)
Definition: sensor.py:606
None __init__(self, HKDevice conn, ConfigType info, Characteristic char, HomeKitSensorEntityDescription description)
Definition: sensor.py:554
StateType|date|datetime|Decimal native_value(self)
Definition: __init__.py:460
BLEDevice|None async_ble_device_from_address(HomeAssistant hass, str address, bool connectable=True)
Definition: api.py:88
BluetoothServiceInfoBleak|None async_last_service_info(HomeAssistant hass, str address, bool connectable=True)
Definition: api.py:80
bool async_add_characteristic(Characteristic char)
Definition: number.py:78
str thread_node_capability_to_str(Characteristic char)
Definition: sensor.py:64
str thread_status_to_str(Characteristic char)
Definition: sensor.py:101
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:644