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."""
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__)

View file

@ -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."""

View file

@ -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(