Use SensorEntityDescription class for Xiaomi Miio (#53890)

This commit is contained in:
Maciej Bieniek 2021-08-03 13:56:56 +02:00 committed by GitHub
parent 7e63e12ece
commit 1286734ce9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,4 +1,6 @@
"""Support for Xiaomi Mi Air Quality Monitor (PM2.5) and Humidifier.""" """Support for Xiaomi Mi Air Quality Monitor (PM2.5) and Humidifier."""
from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
import logging import logging
@ -17,6 +19,7 @@ from homeassistant.components.sensor import (
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
SensorEntity, SensorEntity,
SensorEntityDescription,
) )
from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import ( from homeassistant.const import (
@ -45,6 +48,7 @@ from .const import (
DOMAIN, DOMAIN,
KEY_COORDINATOR, KEY_COORDINATOR,
KEY_DEVICE, KEY_DEVICE,
MODELS_HUMIDIFIER_MIIO,
MODELS_HUMIDIFIER_MIOT, MODELS_HUMIDIFIER_MIOT,
MODELS_HUMIDIFIER_MJJSQ, MODELS_HUMIDIFIER_MJJSQ,
) )
@ -64,79 +68,104 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
} }
) )
ATTR_POWER = "power" ATTR_ACTUAL_MOTOR_SPEED = "actual_speed"
ATTR_AIR_QUALITY = "air_quality"
ATTR_CHARGING = "charging" ATTR_CHARGING = "charging"
ATTR_DISPLAY_CLOCK = "display_clock" ATTR_DISPLAY_CLOCK = "display_clock"
ATTR_HUMIDITY = "humidity"
ATTR_ILLUMINANCE = "illuminance"
ATTR_LOAD_POWER = "load_power"
ATTR_NIGHT_MODE = "night_mode" ATTR_NIGHT_MODE = "night_mode"
ATTR_NIGHT_TIME_BEGIN = "night_time_begin" ATTR_NIGHT_TIME_BEGIN = "night_time_begin"
ATTR_NIGHT_TIME_END = "night_time_end" ATTR_NIGHT_TIME_END = "night_time_end"
ATTR_POWER = "power"
ATTR_PRESSURE = "pressure"
ATTR_SENSOR_STATE = "sensor_state" ATTR_SENSOR_STATE = "sensor_state"
ATTR_WATER_LEVEL = "water_level" ATTR_WATER_LEVEL = "water_level"
ATTR_HUMIDITY = "humidity"
ATTR_ACTUAL_MOTOR_SPEED = "actual_speed"
@dataclass @dataclass
class SensorType: class XiaomiMiioSensorDescription(SensorEntityDescription):
"""Class that holds device specific info for a xiaomi aqara or humidifier sensor.""" """Class that holds device specific info for a xiaomi aqara or humidifier sensor."""
unit: str = None valid_min_value: float | None = None
icon: str = None valid_max_value: float | None = None
device_class: str = None
state_class: str = None
valid_min_value: float = None
valid_max_value: float = None
SENSOR_TYPES = { SENSOR_TYPES = {
"temperature": SensorType( ATTR_TEMPERATURE: XiaomiMiioSensorDescription(
unit=TEMP_CELSIUS, key=ATTR_TEMPERATURE,
name="Temperature",
unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
"humidity": SensorType( ATTR_HUMIDITY: XiaomiMiioSensorDescription(
unit=PERCENTAGE, key=ATTR_HUMIDITY,
name="Humidity",
unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
"pressure": SensorType( ATTR_PRESSURE: XiaomiMiioSensorDescription(
unit=PRESSURE_HPA, key=ATTR_PRESSURE,
name="Pressure",
unit_of_measurement=PRESSURE_HPA,
device_class=DEVICE_CLASS_PRESSURE, device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
"load_power": SensorType( ATTR_LOAD_POWER: XiaomiMiioSensorDescription(
unit=POWER_WATT, key=ATTR_LOAD_POWER,
name="Load Power",
unit_of_measurement=POWER_WATT,
device_class=DEVICE_CLASS_POWER, device_class=DEVICE_CLASS_POWER,
), ),
"water_level": SensorType( ATTR_WATER_LEVEL: XiaomiMiioSensorDescription(
unit=PERCENTAGE, key=ATTR_WATER_LEVEL,
name="Water Level",
unit_of_measurement=PERCENTAGE,
icon="mdi:water-check", icon="mdi:water-check",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
valid_min_value=0.0, valid_min_value=0.0,
valid_max_value=100.0, valid_max_value=100.0,
), ),
"actual_speed": SensorType( ATTR_ACTUAL_MOTOR_SPEED: XiaomiMiioSensorDescription(
unit="rpm", key=ATTR_ACTUAL_MOTOR_SPEED,
name="Actual Speed",
unit_of_measurement="rpm",
icon="mdi:fast-forward", icon="mdi:fast-forward",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
valid_min_value=200.0, valid_min_value=200.0,
valid_max_value=2000.0, valid_max_value=2000.0,
), ),
ATTR_ILLUMINANCE: XiaomiMiioSensorDescription(
key=ATTR_ILLUMINANCE,
name="Illuminance",
unit_of_measurement=UNIT_LUMEN,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
ATTR_AIR_QUALITY: XiaomiMiioSensorDescription(
key=ATTR_AIR_QUALITY,
unit_of_measurement="AQI",
icon="mdi:cloud",
state_class=STATE_CLASS_MEASUREMENT,
),
} }
HUMIDIFIER_SENSORS = { HUMIDIFIER_MIIO_SENSORS = {
ATTR_HUMIDITY: "humidity", ATTR_HUMIDITY: "humidity",
ATTR_TEMPERATURE: "temperature", ATTR_TEMPERATURE: "temperature",
} }
HUMIDIFIER_SENSORS_MIOT = { HUMIDIFIER_MIOT_SENSORS = {
ATTR_HUMIDITY: "humidity", ATTR_HUMIDITY: "humidity",
ATTR_TEMPERATURE: "temperature", ATTR_TEMPERATURE: "temperature",
ATTR_WATER_LEVEL: "water_level", ATTR_WATER_LEVEL: "water_level",
ATTR_ACTUAL_MOTOR_SPEED: "actual_speed", ATTR_ACTUAL_MOTOR_SPEED: "actual_speed",
} }
HUMIDIFIER_SENSORS_MJJSQ = { HUMIDIFIER_MJJSQ_SENSORS = {
ATTR_HUMIDITY: "humidity", ATTR_HUMIDITY: "humidity",
ATTR_TEMPERATURE: "temperature", ATTR_TEMPERATURE: "temperature",
} }
@ -170,24 +199,23 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
GATEWAY_MODEL_AC_V3, GATEWAY_MODEL_AC_V3,
GATEWAY_MODEL_EU, GATEWAY_MODEL_EU,
]: ]:
description = SENSOR_TYPES[ATTR_ILLUMINANCE]
entities.append( entities.append(
XiaomiGatewayIlluminanceSensor( XiaomiGatewayIlluminanceSensor(
gateway, config_entry.title, config_entry.unique_id gateway, config_entry.title, config_entry.unique_id, description
) )
) )
# Gateway sub devices # Gateway sub devices
sub_devices = gateway.devices sub_devices = gateway.devices
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR] coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
for sub_device in sub_devices.values(): for sub_device in sub_devices.values():
sensor_variables = set(sub_device.status) & set(SENSOR_TYPES) for sensor, description in SENSOR_TYPES.items():
if sensor_variables: if sensor not in sub_device.status:
entities.extend( continue
[ entities.append(
XiaomiGatewaySensor( XiaomiGatewaySensor(
coordinator, sub_device, config_entry, variable coordinator, sub_device, config_entry, description
) )
for variable in sensor_variables
]
) )
elif config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: elif config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE:
host = config_entry.data[CONF_HOST] host = config_entry.data[CONF_HOST]
@ -197,31 +225,36 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
sensors = [] sensors = []
if model in MODELS_HUMIDIFIER_MIOT: if model in MODELS_HUMIDIFIER_MIOT:
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE] device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
sensors = HUMIDIFIER_SENSORS_MIOT sensors = HUMIDIFIER_MIOT_SENSORS
elif model in MODELS_HUMIDIFIER_MJJSQ: elif model in MODELS_HUMIDIFIER_MJJSQ:
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE] device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
sensors = HUMIDIFIER_SENSORS_MJJSQ sensors = HUMIDIFIER_MJJSQ_SENSORS
elif model.startswith("zhimi.humidifier."): elif model in MODELS_HUMIDIFIER_MIIO:
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE] device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
sensors = HUMIDIFIER_SENSORS sensors = HUMIDIFIER_MIIO_SENSORS
else: else:
unique_id = config_entry.unique_id unique_id = config_entry.unique_id
name = config_entry.title name = config_entry.title
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
device = AirQualityMonitor(host, token) device = AirQualityMonitor(host, token)
description = SENSOR_TYPES[ATTR_AIR_QUALITY]
entities.append( entities.append(
XiaomiAirQualityMonitor(name, device, config_entry, unique_id) XiaomiAirQualityMonitor(
name, device, config_entry, unique_id, description
)
) )
for sensor in sensors: for sensor, description in SENSOR_TYPES.items():
if sensor not in sensors:
continue
entities.append( entities.append(
XiaomiGenericSensor( XiaomiGenericSensor(
f"{config_entry.title} {sensor.replace('_', ' ').title()}", f"{config_entry.title} {description.name}",
device, device,
config_entry, config_entry,
f"{sensor}_{config_entry.unique_id}", f"{sensor}_{config_entry.unique_id}",
sensor,
hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR], hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR],
description,
) )
) )
@ -231,34 +264,27 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class XiaomiGenericSensor(XiaomiCoordinatedMiioEntity, SensorEntity): class XiaomiGenericSensor(XiaomiCoordinatedMiioEntity, SensorEntity):
"""Representation of a Xiaomi Humidifier sensor.""" """Representation of a Xiaomi Humidifier sensor."""
def __init__(self, name, device, entry, unique_id, attribute, coordinator): def __init__(self, name, device, entry, unique_id, coordinator, description):
"""Initialize the entity.""" """Initialize the entity."""
super().__init__(name, device, entry, unique_id, coordinator) super().__init__(name, device, entry, unique_id, coordinator)
self._sensor_config = SENSOR_TYPES[attribute]
self._attr_device_class = self._sensor_config.device_class
self._attr_state_class = self._sensor_config.state_class
self._attr_icon = self._sensor_config.icon
self._attr_name = name self._attr_name = name
self._attr_unique_id = unique_id self._attr_unique_id = unique_id
self._attr_unit_of_measurement = self._sensor_config.unit
self._device = device
self._entry = entry
self._attribute = attribute
self._state = None self._state = None
self.entity_description = description
@property @property
def state(self): def state(self):
"""Return the state of the device.""" """Return the state of the device."""
self._state = self._extract_value_from_attribute( self._state = self._extract_value_from_attribute(
self.coordinator.data, self._attribute self.coordinator.data, self.entity_description.key
) )
if ( if (
self._sensor_config.valid_min_value self.entity_description.valid_min_value
and self._state < self._sensor_config.valid_min_value and self._state < self.entity_description.valid_min_value
) or ( ) or (
self._sensor_config.valid_max_value self.entity_description.valid_max_value
and self._state > self._sensor_config.valid_max_value and self._state > self.entity_description.valid_max_value
): ):
return None return None
return self._state return self._state
@ -275,12 +301,10 @@ class XiaomiGenericSensor(XiaomiCoordinatedMiioEntity, SensorEntity):
class XiaomiAirQualityMonitor(XiaomiMiioEntity, SensorEntity): class XiaomiAirQualityMonitor(XiaomiMiioEntity, SensorEntity):
"""Representation of a Xiaomi Air Quality Monitor.""" """Representation of a Xiaomi Air Quality Monitor."""
def __init__(self, name, device, entry, unique_id): def __init__(self, name, device, entry, unique_id, description):
"""Initialize the entity.""" """Initialize the entity."""
super().__init__(name, device, entry, unique_id) super().__init__(name, device, entry, unique_id)
self._icon = "mdi:cloud"
self._unit_of_measurement = "AQI"
self._available = None self._available = None
self._state = None self._state = None
self._state_attrs = { self._state_attrs = {
@ -293,16 +317,7 @@ class XiaomiAirQualityMonitor(XiaomiMiioEntity, SensorEntity):
ATTR_NIGHT_TIME_END: None, ATTR_NIGHT_TIME_END: None,
ATTR_SENSOR_STATE: None, ATTR_SENSOR_STATE: None,
} }
self.entity_description = description
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement
@property
def icon(self):
"""Return the icon to use in the frontend."""
return self._icon
@property @property
def available(self): def available(self):
@ -349,69 +364,33 @@ class XiaomiAirQualityMonitor(XiaomiMiioEntity, SensorEntity):
class XiaomiGatewaySensor(XiaomiGatewayDevice, SensorEntity): class XiaomiGatewaySensor(XiaomiGatewayDevice, SensorEntity):
"""Representation of a XiaomiGatewaySensor.""" """Representation of a XiaomiGatewaySensor."""
def __init__(self, coordinator, sub_device, entry, data_key): def __init__(self, coordinator, sub_device, entry, description):
"""Initialize the XiaomiSensor.""" """Initialize the XiaomiSensor."""
super().__init__(coordinator, sub_device, entry) super().__init__(coordinator, sub_device, entry)
self._data_key = data_key self._unique_id = f"{sub_device.sid}-{description.key}"
self._unique_id = f"{sub_device.sid}-{data_key}" self._name = f"{description.key} ({sub_device.sid})".capitalize()
self._name = f"{data_key} ({sub_device.sid})".capitalize() self.entity_description = description
@property
def icon(self):
"""Return the icon to use in the frontend."""
return SENSOR_TYPES[self._data_key].icon
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return SENSOR_TYPES[self._data_key].unit
@property
def device_class(self):
"""Return the device class of this entity."""
return SENSOR_TYPES[self._data_key].device_class
@property
def state_class(self):
"""Return the state class of this entity."""
return SENSOR_TYPES[self._data_key].state_class
@property @property
def state(self): def state(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._sub_device.status[self._data_key] return self._sub_device.status[self.entity_description.key]
class XiaomiGatewayIlluminanceSensor(SensorEntity): class XiaomiGatewayIlluminanceSensor(SensorEntity):
"""Representation of the gateway device's illuminance sensor.""" """Representation of the gateway device's illuminance sensor."""
_attr_device_class = DEVICE_CLASS_ILLUMINANCE def __init__(self, gateway_device, gateway_name, gateway_device_id, description):
_attr_unit_of_measurement = UNIT_LUMEN
def __init__(self, gateway_device, gateway_name, gateway_device_id):
"""Initialize the entity.""" """Initialize the entity."""
self._attr_name = f"{gateway_name} {description.name}"
self._attr_unique_id = f"{gateway_device_id}-{description.key}"
self._attr_device_info = {"identifiers": {(DOMAIN, gateway_device_id)}}
self._gateway = gateway_device self._gateway = gateway_device
self._name = f"{gateway_name} Illuminance" self.entity_description = description
self._gateway_device_id = gateway_device_id
self._unique_id = f"{gateway_device_id}-illuminance"
self._available = False self._available = False
self._state = None self._state = None
@property
def unique_id(self):
"""Return an unique ID."""
return self._unique_id
@property
def device_info(self):
"""Return the device info of the gateway."""
return {"identifiers": {(DOMAIN, self._gateway_device_id)}}
@property
def name(self):
"""Return the name of this entity, if any."""
return self._name
@property @property
def available(self): def available(self):
"""Return true when state is known.""" """Return true when state is known."""