Convert to using sensor descriptors (#54115)
This commit is contained in:
parent
ddbd455827
commit
13e7cd237e
3 changed files with 232 additions and 107 deletions
|
@ -1,7 +1,6 @@
|
|||
"""Support for RFXtrx devices."""
|
||||
import asyncio
|
||||
import binascii
|
||||
from collections import OrderedDict
|
||||
import copy
|
||||
import functools
|
||||
import logging
|
||||
|
@ -22,20 +21,7 @@ from homeassistant.const import (
|
|||
CONF_DEVICES,
|
||||
CONF_HOST,
|
||||
CONF_PORT,
|
||||
DEGREE,
|
||||
ELECTRIC_CURRENT_AMPERE,
|
||||
ELECTRIC_POTENTIAL_VOLT,
|
||||
ENERGY_KILO_WATT_HOUR,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
LENGTH_MILLIMETERS,
|
||||
PERCENTAGE,
|
||||
POWER_WATT,
|
||||
PRECIPITATION_MILLIMETERS_PER_HOUR,
|
||||
PRESSURE_HPA,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
SPEED_METERS_PER_SECOND,
|
||||
TEMP_CELSIUS,
|
||||
UV_INDEX,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -66,38 +52,6 @@ DEFAULT_SIGNAL_REPETITIONS = 1
|
|||
|
||||
SIGNAL_EVENT = f"{DOMAIN}_event"
|
||||
|
||||
DATA_TYPES = OrderedDict(
|
||||
[
|
||||
("Temperature", TEMP_CELSIUS),
|
||||
("Temperature2", TEMP_CELSIUS),
|
||||
("Humidity", PERCENTAGE),
|
||||
("Barometer", PRESSURE_HPA),
|
||||
("Wind direction", DEGREE),
|
||||
("Rain rate", PRECIPITATION_MILLIMETERS_PER_HOUR),
|
||||
("Energy usage", POWER_WATT),
|
||||
("Total usage", ENERGY_KILO_WATT_HOUR),
|
||||
("Sound", None),
|
||||
("Sensor Status", None),
|
||||
("Counter value", "count"),
|
||||
("UV", UV_INDEX),
|
||||
("Humidity status", None),
|
||||
("Forecast", None),
|
||||
("Forecast numeric", None),
|
||||
("Rain total", LENGTH_MILLIMETERS),
|
||||
("Wind average speed", SPEED_METERS_PER_SECOND),
|
||||
("Wind gust", SPEED_METERS_PER_SECOND),
|
||||
("Chill", TEMP_CELSIUS),
|
||||
("Count", "count"),
|
||||
("Current Ch. 1", ELECTRIC_CURRENT_AMPERE),
|
||||
("Current Ch. 2", ELECTRIC_CURRENT_AMPERE),
|
||||
("Current Ch. 3", ELECTRIC_CURRENT_AMPERE),
|
||||
("Voltage", ELECTRIC_POTENTIAL_VOLT),
|
||||
("Current", ELECTRIC_CURRENT_AMPERE),
|
||||
("Battery numeric", PERCENTAGE),
|
||||
("Rssi numeric", SIGNAL_STRENGTH_DECIBELS_MILLIWATT),
|
||||
]
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
"""Support for RFXtrx binary sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import replace
|
||||
import logging
|
||||
|
||||
import RFXtrx as rfxtrxmod
|
||||
|
@ -7,6 +10,7 @@ from homeassistant.components.binary_sensor import (
|
|||
DEVICE_CLASS_MOTION,
|
||||
DEVICE_CLASS_SMOKE,
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_COMMAND_OFF,
|
||||
|
@ -51,13 +55,30 @@ SENSOR_STATUS_OFF = [
|
|||
"Normal Tamper",
|
||||
]
|
||||
|
||||
DEVICE_TYPE_DEVICE_CLASS = {
|
||||
"X10 Security Motion Detector": DEVICE_CLASS_MOTION,
|
||||
"KD101 Smoke Detector": DEVICE_CLASS_SMOKE,
|
||||
"Visonic Powercode Motion Detector": DEVICE_CLASS_MOTION,
|
||||
"Alecto SA30 Smoke Detector": DEVICE_CLASS_SMOKE,
|
||||
"RM174RF Smoke Detector": DEVICE_CLASS_SMOKE,
|
||||
}
|
||||
SENSOR_TYPES = (
|
||||
BinarySensorEntityDescription(
|
||||
key="X10 Security Motion Detector",
|
||||
device_class=DEVICE_CLASS_MOTION,
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key="KD101 Smoke Detector",
|
||||
device_class=DEVICE_CLASS_SMOKE,
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key="Visonic Powercode Motion Detector",
|
||||
device_class=DEVICE_CLASS_MOTION,
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key="Alecto SA30 Smoke Detector",
|
||||
device_class=DEVICE_CLASS_SMOKE,
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key="RM174RF Smoke Detector",
|
||||
device_class=DEVICE_CLASS_SMOKE,
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_TYPES_DICT = {desc.key: desc for desc in SENSOR_TYPES}
|
||||
|
||||
|
||||
def supported(event):
|
||||
|
@ -85,6 +106,14 @@ async def async_setup_entry(
|
|||
|
||||
discovery_info = config_entry.data
|
||||
|
||||
def get_sensor_description(type_string: str, device_class: str | None = None):
|
||||
description = SENSOR_TYPES_DICT.get(type_string)
|
||||
if description is None:
|
||||
description = BinarySensorEntityDescription(key=type_string)
|
||||
if device_class:
|
||||
description = replace(description, device_class=device)
|
||||
return description
|
||||
|
||||
for packet_id, entity_info in discovery_info[CONF_DEVICES].items():
|
||||
event = get_rfx_object(packet_id)
|
||||
if event is None:
|
||||
|
@ -107,9 +136,8 @@ async def async_setup_entry(
|
|||
device = RfxtrxBinarySensor(
|
||||
event.device,
|
||||
device_id,
|
||||
entity_info.get(
|
||||
CONF_DEVICE_CLASS,
|
||||
DEVICE_TYPE_DEVICE_CLASS.get(event.device.type_string),
|
||||
get_sensor_description(
|
||||
event.device.type_string, entity_info.get(CONF_DEVICE_CLASS)
|
||||
),
|
||||
entity_info.get(CONF_OFF_DELAY),
|
||||
entity_info.get(CONF_DATA_BITS),
|
||||
|
@ -137,11 +165,12 @@ async def async_setup_entry(
|
|||
event.device.subtype,
|
||||
"".join(f"{x:02x}" for x in event.data),
|
||||
)
|
||||
|
||||
sensor = RfxtrxBinarySensor(
|
||||
event.device,
|
||||
device_id,
|
||||
event=event,
|
||||
device_class=DEVICE_TYPE_DEVICE_CLASS.get(event.device.type_string),
|
||||
entity_description=get_sensor_description(event.device.type_string),
|
||||
)
|
||||
async_add_entities([sensor])
|
||||
|
||||
|
@ -156,7 +185,7 @@ class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity):
|
|||
self,
|
||||
device,
|
||||
device_id,
|
||||
device_class=None,
|
||||
entity_description,
|
||||
off_delay=None,
|
||||
data_bits=None,
|
||||
cmd_on=None,
|
||||
|
@ -165,7 +194,7 @@ class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity):
|
|||
):
|
||||
"""Initialize the RFXtrx sensor."""
|
||||
super().__init__(device, device_id, event=event)
|
||||
self._device_class = device_class
|
||||
self.entity_description = entity_description
|
||||
self._data_bits = data_bits
|
||||
self._off_delay = off_delay
|
||||
self._state = None
|
||||
|
@ -190,11 +219,6 @@ class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity):
|
|||
"""We should force updates. Repeated states have meaning."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the sensor class."""
|
||||
return self._device_class
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the sensor state is True."""
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
"""Support for RFXtrx sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from RFXtrx import ControlEvent, SensorEvent
|
||||
|
||||
|
@ -8,21 +12,36 @@ from homeassistant.components.sensor import (
|
|||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICES,
|
||||
DEGREE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ELECTRIC_CURRENT_AMPERE,
|
||||
ELECTRIC_POTENTIAL_VOLT,
|
||||
ENERGY_KILO_WATT_HOUR,
|
||||
LENGTH_MILLIMETERS,
|
||||
PERCENTAGE,
|
||||
POWER_WATT,
|
||||
PRECIPITATION_MILLIMETERS_PER_HOUR,
|
||||
PRESSURE_HPA,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
SPEED_METERS_PER_SECOND,
|
||||
TEMP_CELSIUS,
|
||||
UV_INDEX,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.util import dt
|
||||
|
||||
from . import (
|
||||
CONF_DATA_BITS,
|
||||
DATA_TYPES,
|
||||
RfxtrxEntity,
|
||||
connect_auto_add,
|
||||
get_device_id,
|
||||
|
@ -47,25 +66,161 @@ def _rssi_convert(value):
|
|||
return f"{value*8-120}"
|
||||
|
||||
|
||||
DEVICE_CLASSES = {
|
||||
"Barometer": DEVICE_CLASS_PRESSURE,
|
||||
"Battery numeric": DEVICE_CLASS_BATTERY,
|
||||
"Current Ch. 1": DEVICE_CLASS_CURRENT,
|
||||
"Current Ch. 2": DEVICE_CLASS_CURRENT,
|
||||
"Current Ch. 3": DEVICE_CLASS_CURRENT,
|
||||
"Energy usage": DEVICE_CLASS_POWER,
|
||||
"Humidity": DEVICE_CLASS_HUMIDITY,
|
||||
"Rssi numeric": DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
"Temperature": DEVICE_CLASS_TEMPERATURE,
|
||||
"Total usage": DEVICE_CLASS_ENERGY,
|
||||
"Voltage": DEVICE_CLASS_VOLTAGE,
|
||||
}
|
||||
@dataclass
|
||||
class RfxtrxSensorEntityDescription(SensorEntityDescription):
|
||||
"""Description of sensor entities."""
|
||||
|
||||
convert: Callable = lambda x: x
|
||||
|
||||
|
||||
CONVERT_FUNCTIONS = {
|
||||
"Battery numeric": _battery_convert,
|
||||
"Rssi numeric": _rssi_convert,
|
||||
}
|
||||
SENSOR_TYPES = (
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Barameter",
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=PRESSURE_HPA,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Battery numeric",
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=PERCENTAGE,
|
||||
convert=_battery_convert,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Current",
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Current Ch. 1",
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Current Ch. 2",
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Current Ch. 3",
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Energy usage",
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=POWER_WATT,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Humidity",
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Rssi numeric",
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
convert=_rssi_convert,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Temperature",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=TEMP_CELSIUS,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Temperature2",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=TEMP_CELSIUS,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Total usage",
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
last_reset=dt.utc_from_timestamp(0),
|
||||
unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Voltage",
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Wind direction",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=DEGREE,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Rain rate",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=PRECIPITATION_MILLIMETERS_PER_HOUR,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Sound",
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Sensor Status",
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Count",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
last_reset=dt.utc_from_timestamp(0),
|
||||
unit_of_measurement="count",
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Counter value",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
last_reset=dt.utc_from_timestamp(0),
|
||||
unit_of_measurement="count",
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Chill",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=TEMP_CELSIUS,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Wind average speed",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=SPEED_METERS_PER_SECOND,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Wind gust",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=SPEED_METERS_PER_SECOND,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Rain total",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=LENGTH_MILLIMETERS,
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Forecast",
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Forecast numeric",
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="Humidity status",
|
||||
),
|
||||
RfxtrxSensorEntityDescription(
|
||||
key="UV",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UV_INDEX,
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_TYPES_DICT = {desc.key: desc for desc in SENSOR_TYPES}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@ -92,13 +247,13 @@ async def async_setup_entry(
|
|||
device_id = get_device_id(
|
||||
event.device, data_bits=entity_info.get(CONF_DATA_BITS)
|
||||
)
|
||||
for data_type in set(event.values) & set(DATA_TYPES):
|
||||
for data_type in set(event.values) & set(SENSOR_TYPES_DICT):
|
||||
data_id = (*device_id, data_type)
|
||||
if data_id in data_ids:
|
||||
continue
|
||||
data_ids.add(data_id)
|
||||
|
||||
entity = RfxtrxSensor(event.device, device_id, data_type)
|
||||
entity = RfxtrxSensor(event.device, device_id, SENSOR_TYPES_DICT[data_type])
|
||||
entities.append(entity)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
@ -109,7 +264,7 @@ async def async_setup_entry(
|
|||
if not supported(event):
|
||||
return
|
||||
|
||||
for data_type in set(event.values) & set(DATA_TYPES):
|
||||
for data_type in set(event.values) & set(SENSOR_TYPES_DICT):
|
||||
data_id = (*device_id, data_type)
|
||||
if data_id in data_ids:
|
||||
continue
|
||||
|
@ -123,7 +278,9 @@ async def async_setup_entry(
|
|||
"".join(f"{x:02x}" for x in event.data),
|
||||
)
|
||||
|
||||
entity = RfxtrxSensor(event.device, device_id, data_type, event=event)
|
||||
entity = RfxtrxSensor(
|
||||
event.device, device_id, SENSOR_TYPES_DICT[data_type], event=event
|
||||
)
|
||||
async_add_entities([entity])
|
||||
|
||||
# Subscribe to main RFXtrx events
|
||||
|
@ -133,16 +290,16 @@ async def async_setup_entry(
|
|||
class RfxtrxSensor(RfxtrxEntity, SensorEntity):
|
||||
"""Representation of a RFXtrx sensor."""
|
||||
|
||||
def __init__(self, device, device_id, data_type, event=None):
|
||||
entity_description: RfxtrxSensorEntityDescription
|
||||
|
||||
def __init__(self, device, device_id, entity_description, event=None):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(device, device_id, event=event)
|
||||
self.data_type = data_type
|
||||
self._unit_of_measurement = DATA_TYPES.get(data_type)
|
||||
self._name = f"{device.type_string} {device.id_string} {data_type}"
|
||||
self._unique_id = "_".join(x for x in (*self._device_id, data_type))
|
||||
|
||||
self._device_class = DEVICE_CLASSES.get(data_type)
|
||||
self._convert_fun = CONVERT_FUNCTIONS.get(data_type, lambda x: x)
|
||||
self.entity_description = entity_description
|
||||
self._name = f"{device.type_string} {device.id_string} {entity_description.key}"
|
||||
self._unique_id = "_".join(
|
||||
x for x in (*self._device_id, entity_description.key)
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Restore device state."""
|
||||
|
@ -160,13 +317,8 @@ class RfxtrxSensor(RfxtrxEntity, SensorEntity):
|
|||
"""Return the state of the sensor."""
|
||||
if not self._event:
|
||||
return None
|
||||
value = self._event.values.get(self.data_type)
|
||||
return self._convert_fun(value)
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit this state is expressed in."""
|
||||
return self._unit_of_measurement
|
||||
value = self._event.values.get(self.entity_description.key)
|
||||
return self.entity_description.convert(value)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
|
@ -178,18 +330,13 @@ class RfxtrxSensor(RfxtrxEntity, SensorEntity):
|
|||
"""We should force updates. Repeated states have meaning."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return a device class for sensor."""
|
||||
return self._device_class
|
||||
|
||||
@callback
|
||||
def _handle_event(self, event, device_id):
|
||||
"""Check if event applies to me and update."""
|
||||
if device_id != self._device_id:
|
||||
return
|
||||
|
||||
if self.data_type not in event.values:
|
||||
if self.entity_description.key not in event.values:
|
||||
return
|
||||
|
||||
_LOGGER.debug(
|
||||
|
|
Loading…
Add table
Reference in a new issue