Convert to using sensor descriptors (#54115)

This commit is contained in:
Joakim Plate 2021-08-06 18:56:51 +02:00 committed by GitHub
parent ddbd455827
commit 13e7cd237e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 232 additions and 107 deletions

View file

@ -1,7 +1,6 @@
"""Support for RFXtrx devices.""" """Support for RFXtrx devices."""
import asyncio import asyncio
import binascii import binascii
from collections import OrderedDict
import copy import copy
import functools import functools
import logging import logging
@ -22,20 +21,7 @@ from homeassistant.const import (
CONF_DEVICES, CONF_DEVICES,
CONF_HOST, CONF_HOST,
CONF_PORT, CONF_PORT,
DEGREE,
ELECTRIC_CURRENT_AMPERE,
ELECTRIC_POTENTIAL_VOLT,
ENERGY_KILO_WATT_HOUR,
EVENT_HOMEASSISTANT_STOP, 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 from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -66,38 +52,6 @@ DEFAULT_SIGNAL_REPETITIONS = 1
SIGNAL_EVENT = f"{DOMAIN}_event" 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__) _LOGGER = logging.getLogger(__name__)

View file

@ -1,4 +1,7 @@
"""Support for RFXtrx binary sensors.""" """Support for RFXtrx binary sensors."""
from __future__ import annotations
from dataclasses import replace
import logging import logging
import RFXtrx as rfxtrxmod import RFXtrx as rfxtrxmod
@ -7,6 +10,7 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASS_MOTION, DEVICE_CLASS_MOTION,
DEVICE_CLASS_SMOKE, DEVICE_CLASS_SMOKE,
BinarySensorEntity, BinarySensorEntity,
BinarySensorEntityDescription,
) )
from homeassistant.const import ( from homeassistant.const import (
CONF_COMMAND_OFF, CONF_COMMAND_OFF,
@ -51,13 +55,30 @@ SENSOR_STATUS_OFF = [
"Normal Tamper", "Normal Tamper",
] ]
DEVICE_TYPE_DEVICE_CLASS = { SENSOR_TYPES = (
"X10 Security Motion Detector": DEVICE_CLASS_MOTION, BinarySensorEntityDescription(
"KD101 Smoke Detector": DEVICE_CLASS_SMOKE, key="X10 Security Motion Detector",
"Visonic Powercode Motion Detector": DEVICE_CLASS_MOTION, device_class=DEVICE_CLASS_MOTION,
"Alecto SA30 Smoke Detector": DEVICE_CLASS_SMOKE, ),
"RM174RF Smoke Detector": DEVICE_CLASS_SMOKE, 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): def supported(event):
@ -85,6 +106,14 @@ async def async_setup_entry(
discovery_info = config_entry.data 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(): for packet_id, entity_info in discovery_info[CONF_DEVICES].items():
event = get_rfx_object(packet_id) event = get_rfx_object(packet_id)
if event is None: if event is None:
@ -107,9 +136,8 @@ async def async_setup_entry(
device = RfxtrxBinarySensor( device = RfxtrxBinarySensor(
event.device, event.device,
device_id, device_id,
entity_info.get( get_sensor_description(
CONF_DEVICE_CLASS, event.device.type_string, entity_info.get(CONF_DEVICE_CLASS)
DEVICE_TYPE_DEVICE_CLASS.get(event.device.type_string),
), ),
entity_info.get(CONF_OFF_DELAY), entity_info.get(CONF_OFF_DELAY),
entity_info.get(CONF_DATA_BITS), entity_info.get(CONF_DATA_BITS),
@ -137,11 +165,12 @@ async def async_setup_entry(
event.device.subtype, event.device.subtype,
"".join(f"{x:02x}" for x in event.data), "".join(f"{x:02x}" for x in event.data),
) )
sensor = RfxtrxBinarySensor( sensor = RfxtrxBinarySensor(
event.device, event.device,
device_id, device_id,
event=event, 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]) async_add_entities([sensor])
@ -156,7 +185,7 @@ class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity):
self, self,
device, device,
device_id, device_id,
device_class=None, entity_description,
off_delay=None, off_delay=None,
data_bits=None, data_bits=None,
cmd_on=None, cmd_on=None,
@ -165,7 +194,7 @@ class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity):
): ):
"""Initialize the RFXtrx sensor.""" """Initialize the RFXtrx sensor."""
super().__init__(device, device_id, event=event) super().__init__(device, device_id, event=event)
self._device_class = device_class self.entity_description = entity_description
self._data_bits = data_bits self._data_bits = data_bits
self._off_delay = off_delay self._off_delay = off_delay
self._state = None self._state = None
@ -190,11 +219,6 @@ class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity):
"""We should force updates. Repeated states have meaning.""" """We should force updates. Repeated states have meaning."""
return True return True
@property
def device_class(self):
"""Return the sensor class."""
return self._device_class
@property @property
def is_on(self): def is_on(self):
"""Return true if the sensor state is True.""" """Return true if the sensor state is True."""

View file

@ -1,5 +1,9 @@
"""Support for RFXtrx sensors.""" """Support for RFXtrx sensors."""
from __future__ import annotations
from dataclasses import dataclass
import logging import logging
from typing import Callable
from RFXtrx import ControlEvent, SensorEvent from RFXtrx import ControlEvent, SensorEvent
@ -8,21 +12,36 @@ from homeassistant.components.sensor import (
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
SensorEntity, SensorEntity,
SensorEntityDescription,
) )
from homeassistant.const import ( from homeassistant.const import (
CONF_DEVICES, CONF_DEVICES,
DEGREE,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_VOLTAGE, 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.core import callback
from homeassistant.util import dt
from . import ( from . import (
CONF_DATA_BITS, CONF_DATA_BITS,
DATA_TYPES,
RfxtrxEntity, RfxtrxEntity,
connect_auto_add, connect_auto_add,
get_device_id, get_device_id,
@ -47,25 +66,161 @@ def _rssi_convert(value):
return f"{value*8-120}" return f"{value*8-120}"
DEVICE_CLASSES = { @dataclass
"Barometer": DEVICE_CLASS_PRESSURE, class RfxtrxSensorEntityDescription(SensorEntityDescription):
"Battery numeric": DEVICE_CLASS_BATTERY, """Description of sensor entities."""
"Current Ch. 1": DEVICE_CLASS_CURRENT,
"Current Ch. 2": DEVICE_CLASS_CURRENT, convert: Callable = lambda x: x
"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,
}
CONVERT_FUNCTIONS = { SENSOR_TYPES = (
"Battery numeric": _battery_convert, RfxtrxSensorEntityDescription(
"Rssi numeric": _rssi_convert, 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( async def async_setup_entry(
@ -92,13 +247,13 @@ async def async_setup_entry(
device_id = get_device_id( device_id = get_device_id(
event.device, data_bits=entity_info.get(CONF_DATA_BITS) 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) data_id = (*device_id, data_type)
if data_id in data_ids: if data_id in data_ids:
continue continue
data_ids.add(data_id) 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) entities.append(entity)
async_add_entities(entities) async_add_entities(entities)
@ -109,7 +264,7 @@ async def async_setup_entry(
if not supported(event): if not supported(event):
return 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) data_id = (*device_id, data_type)
if data_id in data_ids: if data_id in data_ids:
continue continue
@ -123,7 +278,9 @@ async def async_setup_entry(
"".join(f"{x:02x}" for x in event.data), "".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]) async_add_entities([entity])
# Subscribe to main RFXtrx events # Subscribe to main RFXtrx events
@ -133,16 +290,16 @@ async def async_setup_entry(
class RfxtrxSensor(RfxtrxEntity, SensorEntity): class RfxtrxSensor(RfxtrxEntity, SensorEntity):
"""Representation of a RFXtrx sensor.""" """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.""" """Initialize the sensor."""
super().__init__(device, device_id, event=event) super().__init__(device, device_id, event=event)
self.data_type = data_type self.entity_description = entity_description
self._unit_of_measurement = DATA_TYPES.get(data_type) self._name = f"{device.type_string} {device.id_string} {entity_description.key}"
self._name = f"{device.type_string} {device.id_string} {data_type}" self._unique_id = "_".join(
self._unique_id = "_".join(x for x in (*self._device_id, data_type)) x for x in (*self._device_id, entity_description.key)
)
self._device_class = DEVICE_CLASSES.get(data_type)
self._convert_fun = CONVERT_FUNCTIONS.get(data_type, lambda x: x)
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Restore device state.""" """Restore device state."""
@ -160,13 +317,8 @@ class RfxtrxSensor(RfxtrxEntity, SensorEntity):
"""Return the state of the sensor.""" """Return the state of the sensor."""
if not self._event: if not self._event:
return None return None
value = self._event.values.get(self.data_type) value = self._event.values.get(self.entity_description.key)
return self._convert_fun(value) return self.entity_description.convert(value)
@property
def unit_of_measurement(self):
"""Return the unit this state is expressed in."""
return self._unit_of_measurement
@property @property
def should_poll(self): def should_poll(self):
@ -178,18 +330,13 @@ class RfxtrxSensor(RfxtrxEntity, SensorEntity):
"""We should force updates. Repeated states have meaning.""" """We should force updates. Repeated states have meaning."""
return True return True
@property
def device_class(self):
"""Return a device class for sensor."""
return self._device_class
@callback @callback
def _handle_event(self, event, device_id): def _handle_event(self, event, device_id):
"""Check if event applies to me and update.""" """Check if event applies to me and update."""
if device_id != self._device_id: if device_id != self._device_id:
return return
if self.data_type not in event.values: if self.entity_description.key not in event.values:
return return
_LOGGER.debug( _LOGGER.debug(