Use EntityDescription - smappee (#56747)

This commit is contained in:
Marc Mueller 2021-09-30 09:15:09 +02:00 committed by GitHub
parent 51addfc164
commit 54abd80462
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,8 +1,13 @@
"""Support for monitoring a Smappee energy sensor.""" """Support for monitoring a Smappee energy sensor."""
from __future__ import annotations
from dataclasses import dataclass, field
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_TOTAL_INCREASING,
SensorEntity, SensorEntity,
SensorEntityDescription,
) )
from homeassistant.const import ( from homeassistant.const import (
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
@ -16,141 +21,177 @@ from homeassistant.const import (
from .const import DOMAIN from .const import DOMAIN
TREND_SENSORS = {
"total_power": [ @dataclass
"Total consumption - Active power", class SmappeeRequiredKeysMixin:
None, """Mixin for required keys."""
POWER_WATT,
"total_power", sensor_id: str
DEVICE_CLASS_POWER,
True, # both cloud and local
], @dataclass
"alwayson": [ class SmappeeSensorEntityDescription(SensorEntityDescription, SmappeeRequiredKeysMixin):
"Always on - Active power", """Describes Smappee sensor entity."""
None,
POWER_WATT,
"alwayson", @dataclass
DEVICE_CLASS_POWER, class SmappeePollingSensorEntityDescription(SmappeeSensorEntityDescription):
False, # cloud only """Describes Smappee sensor entity."""
],
"power_today": [ local_polling: bool = False
"Total consumption - Today",
None,
ENERGY_WATT_HOUR, @dataclass
"power_today", class SmappeeVoltageSensorEntityDescription(SmappeeSensorEntityDescription):
DEVICE_CLASS_ENERGY, """Describes Smappee sensor entity."""
False, # cloud only
], phase_types: set[str] = field(default_factory=set)
"power_current_hour": [
"Total consumption - Current hour",
None, TREND_SENSORS: tuple[SmappeePollingSensorEntityDescription, ...] = (
ENERGY_WATT_HOUR, SmappeePollingSensorEntityDescription(
"power_current_hour", key="total_power",
DEVICE_CLASS_ENERGY, name="Total consumption - Active power",
False, # cloud only native_unit_of_measurement=POWER_WATT,
], sensor_id="total_power",
"power_last_5_minutes": [ device_class=DEVICE_CLASS_POWER,
"Total consumption - Last 5 minutes", state_class=STATE_CLASS_MEASUREMENT,
None, local_polling=True, # both cloud and local
ENERGY_WATT_HOUR, ),
"power_last_5_minutes", SmappeePollingSensorEntityDescription(
DEVICE_CLASS_ENERGY, key="alwayson",
False, # cloud only name="Always on - Active power",
], native_unit_of_measurement=POWER_WATT,
"alwayson_today": [ sensor_id="alwayson",
"Always on - Today", device_class=DEVICE_CLASS_POWER,
None, state_class=STATE_CLASS_MEASUREMENT,
ENERGY_WATT_HOUR, ),
"alwayson_today", SmappeePollingSensorEntityDescription(
DEVICE_CLASS_ENERGY, key="power_today",
False, # cloud only name="Total consumption - Today",
], native_unit_of_measurement=ENERGY_WATT_HOUR,
} sensor_id="power_today",
REACTIVE_SENSORS = { device_class=DEVICE_CLASS_ENERGY,
"total_reactive_power": [ state_class=STATE_CLASS_TOTAL_INCREASING,
"Total consumption - Reactive power", ),
None, SmappeePollingSensorEntityDescription(
POWER_WATT, key="power_current_hour",
"total_reactive_power", name="Total consumption - Current hour",
DEVICE_CLASS_POWER, native_unit_of_measurement=ENERGY_WATT_HOUR,
] sensor_id="power_current_hour",
} device_class=DEVICE_CLASS_ENERGY,
SOLAR_SENSORS = { state_class=STATE_CLASS_TOTAL_INCREASING,
"solar_power": [ ),
"Total production - Active power", SmappeePollingSensorEntityDescription(
None, key="power_last_5_minutes",
POWER_WATT, name="Total consumption - Last 5 minutes",
"solar_power", native_unit_of_measurement=ENERGY_WATT_HOUR,
DEVICE_CLASS_POWER, sensor_id="power_last_5_minutes",
True, # both cloud and local device_class=DEVICE_CLASS_ENERGY,
], state_class=STATE_CLASS_TOTAL_INCREASING,
"solar_today": [ ),
"Total production - Today", SmappeePollingSensorEntityDescription(
None, key="alwayson_today",
ENERGY_WATT_HOUR, name="Always on - Today",
"solar_today", native_unit_of_measurement=ENERGY_WATT_HOUR,
DEVICE_CLASS_ENERGY, sensor_id="alwayson_today",
False, # cloud only device_class=DEVICE_CLASS_ENERGY,
], state_class=STATE_CLASS_TOTAL_INCREASING,
"solar_current_hour": [ ),
"Total production - Current hour", )
None, REACTIVE_SENSORS: tuple[SmappeeSensorEntityDescription, ...] = (
ENERGY_WATT_HOUR, SmappeeSensorEntityDescription(
"solar_current_hour", key="total_reactive_power",
DEVICE_CLASS_ENERGY, name="Total consumption - Reactive power",
False, # cloud only native_unit_of_measurement=POWER_WATT,
], sensor_id="total_reactive_power",
} device_class=DEVICE_CLASS_POWER,
VOLTAGE_SENSORS = { state_class=STATE_CLASS_MEASUREMENT,
"phase_voltages_a": [ ),
"Phase voltages - A", )
None, SOLAR_SENSORS: tuple[SmappeePollingSensorEntityDescription, ...] = (
ELECTRIC_POTENTIAL_VOLT, SmappeePollingSensorEntityDescription(
"phase_voltage_a", key="solar_power",
DEVICE_CLASS_VOLTAGE, name="Total production - Active power",
["ONE", "TWO", "THREE_STAR", "THREE_DELTA"], native_unit_of_measurement=POWER_WATT,
], sensor_id="solar_power",
"phase_voltages_b": [ device_class=DEVICE_CLASS_POWER,
"Phase voltages - B", state_class=STATE_CLASS_MEASUREMENT,
None, local_polling=True, # both cloud and local
ELECTRIC_POTENTIAL_VOLT, ),
"phase_voltage_b", SmappeePollingSensorEntityDescription(
DEVICE_CLASS_VOLTAGE, key="solar_today",
["TWO", "THREE_STAR", "THREE_DELTA"], name="Total production - Today",
], native_unit_of_measurement=ENERGY_WATT_HOUR,
"phase_voltages_c": [ sensor_id="solar_today",
"Phase voltages - C", device_class=DEVICE_CLASS_ENERGY,
None, state_class=STATE_CLASS_TOTAL_INCREASING,
ELECTRIC_POTENTIAL_VOLT, ),
"phase_voltage_c", SmappeePollingSensorEntityDescription(
DEVICE_CLASS_VOLTAGE, key="solar_current_hour",
["THREE_STAR"], name="Total production - Current hour",
], native_unit_of_measurement=ENERGY_WATT_HOUR,
"line_voltages_a": [ sensor_id="solar_current_hour",
"Line voltages - A", device_class=DEVICE_CLASS_ENERGY,
None, state_class=STATE_CLASS_TOTAL_INCREASING,
ELECTRIC_POTENTIAL_VOLT, ),
"line_voltage_a", )
DEVICE_CLASS_VOLTAGE, VOLTAGE_SENSORS: tuple[SmappeeVoltageSensorEntityDescription, ...] = (
["ONE", "TWO", "THREE_STAR", "THREE_DELTA"], SmappeeVoltageSensorEntityDescription(
], key="phase_voltages_a",
"line_voltages_b": [ name="Phase voltages - A",
"Line voltages - B", native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
None, sensor_id="phase_voltage_a",
ELECTRIC_POTENTIAL_VOLT, device_class=DEVICE_CLASS_VOLTAGE,
"line_voltage_b", state_class=STATE_CLASS_MEASUREMENT,
DEVICE_CLASS_VOLTAGE, phase_types={"ONE", "TWO", "THREE_STAR", "THREE_DELTA"},
["TWO", "THREE_STAR", "THREE_DELTA"], ),
], SmappeeVoltageSensorEntityDescription(
"line_voltages_c": [ key="phase_voltages_b",
"Line voltages - C", name="Phase voltages - B",
None, native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
ELECTRIC_POTENTIAL_VOLT, sensor_id="phase_voltage_b",
"line_voltage_c", device_class=DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT,
["THREE_STAR", "THREE_DELTA"], phase_types={"TWO", "THREE_STAR", "THREE_DELTA"},
], ),
} SmappeeVoltageSensorEntityDescription(
key="phase_voltages_c",
name="Phase voltages - C",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
sensor_id="phase_voltage_c",
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
phase_types={"THREE_STAR"},
),
SmappeeVoltageSensorEntityDescription(
key="line_voltages_a",
name="Line voltages - A",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
sensor_id="line_voltage_a",
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
phase_types={"ONE", "TWO", "THREE_STAR", "THREE_DELTA"},
),
SmappeeVoltageSensorEntityDescription(
key="line_voltages_b",
name="Line voltages - B",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
sensor_id="line_voltage_b",
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
phase_types={"TWO", "THREE_STAR", "THREE_DELTA"},
),
SmappeeVoltageSensorEntityDescription(
key="line_voltages_c",
name="Line voltages - C",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
sensor_id="line_voltage_c",
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
phase_types={"THREE_STAR", "THREE_DELTA"},
),
)
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
@ -161,116 +202,125 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
for service_location in smappee_base.smappee.service_locations.values(): for service_location in smappee_base.smappee.service_locations.values():
# Add all basic sensors (realtime values and aggregators) # Add all basic sensors (realtime values and aggregators)
# Some are available in local only env # Some are available in local only env
for sensor, attributes in TREND_SENSORS.items(): entities.extend(
if not service_location.local_polling or attributes[5]: [
entities.append(
SmappeeSensor(
smappee_base=smappee_base,
service_location=service_location,
sensor=sensor,
attributes=attributes,
)
)
if service_location.has_reactive_value:
for reactive_sensor, attributes in REACTIVE_SENSORS.items():
entities.append(
SmappeeSensor(
smappee_base=smappee_base,
service_location=service_location,
sensor=reactive_sensor,
attributes=attributes,
)
)
# Add solar sensors (some are available in local only env)
if service_location.has_solar_production:
for sensor, attributes in SOLAR_SENSORS.items():
if not service_location.local_polling or attributes[5]:
entities.append(
SmappeeSensor(
smappee_base=smappee_base,
service_location=service_location,
sensor=sensor,
attributes=attributes,
)
)
# Add all CT measurements
for measurement_id, measurement in service_location.measurements.items():
entities.append(
SmappeeSensor( SmappeeSensor(
smappee_base=smappee_base, smappee_base=smappee_base,
service_location=service_location, service_location=service_location,
sensor="load", description=description,
attributes=[
measurement.name,
None,
POWER_WATT,
measurement_id,
DEVICE_CLASS_POWER,
],
) )
for description in TREND_SENSORS
if not service_location.local_polling or description.local_polling
]
)
if service_location.has_reactive_value:
entities.extend(
[
SmappeeSensor(
smappee_base=smappee_base,
service_location=service_location,
description=description,
)
for description in REACTIVE_SENSORS
]
) )
# Add solar sensors (some are available in local only env)
if service_location.has_solar_production:
entities.extend(
[
SmappeeSensor(
smappee_base=smappee_base,
service_location=service_location,
description=description,
)
for description in SOLAR_SENSORS
if not service_location.local_polling or description.local_polling
]
)
# Add all CT measurements
entities.extend(
[
SmappeeSensor(
smappee_base=smappee_base,
service_location=service_location,
description=SmappeeSensorEntityDescription(
key="load",
name=measurement.name,
sensor_id=measurement_id,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
)
for measurement_id, measurement in service_location.measurements.items()
]
)
# Add phase- and line voltages if available # Add phase- and line voltages if available
if service_location.has_voltage_values: if service_location.has_voltage_values:
for sensor_name, sensor in VOLTAGE_SENSORS.items(): entities.extend(
if service_location.phase_type in sensor[5]: [
SmappeeSensor(
smappee_base=smappee_base,
service_location=service_location,
description=description,
)
for description in VOLTAGE_SENSORS
if ( if (
sensor_name.startswith("line_") service_location.phase_type in description.phase_types
and service_location.local_polling and not (
): description.key.startswith("line_")
continue and service_location.local_polling
entities.append(
SmappeeSensor(
smappee_base=smappee_base,
service_location=service_location,
sensor=sensor_name,
attributes=sensor,
) )
) )
]
)
# Add Gas and Water sensors # Add Gas and Water sensors
for sensor_id, sensor in service_location.sensors.items(): entities.extend(
for channel in sensor.channels: [
gw_icon = "mdi:gas-cylinder" SmappeeSensor(
if channel.get("type") == "water": smappee_base=smappee_base,
gw_icon = "mdi:water" service_location=service_location,
description=SmappeeSensorEntityDescription(
entities.append( key="sensor",
SmappeeSensor( name=channel.get("name"),
smappee_base=smappee_base, icon=(
service_location=service_location, "mdi:water"
sensor="sensor", if channel.get("type") == "water"
attributes=[ else "mdi:gas-cylinder"
channel.get("name"), ),
gw_icon, native_unit_of_measurement=channel.get("uom"),
channel.get("uom"), sensor_id=f"{sensor_id}-{channel.get('channel')}",
f"{sensor_id}-{channel.get('channel')}", state_class=STATE_CLASS_MEASUREMENT,
None, ),
],
)
) )
for sensor_id, sensor in service_location.sensors.items()
for channel in sensor.channels
]
)
# Add today_energy_kwh sensors for switches # Add today_energy_kwh sensors for switches
for actuator_id, actuator in service_location.actuators.items(): entities.extend(
if actuator.type == "SWITCH": [
entities.append( SmappeeSensor(
SmappeeSensor( smappee_base=smappee_base,
smappee_base=smappee_base, service_location=service_location,
service_location=service_location, description=SmappeeSensorEntityDescription(
sensor="switch", key="switch",
attributes=[ name=f"{actuator.name} - energy today",
f"{actuator.name} - energy today", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
None, sensor_id=actuator_id,
ENERGY_KILO_WATT_HOUR, device_class=DEVICE_CLASS_ENERGY,
actuator_id, state_class=STATE_CLASS_TOTAL_INCREASING,
DEVICE_CLASS_ENERGY, ),
False, # cloud only
],
)
) )
for actuator_id, actuator in service_location.actuators.items()
if actuator.type == "SWITCH"
]
)
async_add_entities(entities, True) async_add_entities(entities, True)
@ -278,84 +328,47 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class SmappeeSensor(SensorEntity): class SmappeeSensor(SensorEntity):
"""Implementation of a Smappee sensor.""" """Implementation of a Smappee sensor."""
def __init__(self, smappee_base, service_location, sensor, attributes): entity_description: SmappeeSensorEntityDescription
def __init__(
self,
smappee_base,
service_location,
description: SmappeeSensorEntityDescription,
):
"""Initialize the Smappee sensor.""" """Initialize the Smappee sensor."""
self.entity_description = description
self._smappee_base = smappee_base self._smappee_base = smappee_base
self._service_location = service_location self._service_location = service_location
self._sensor = sensor
self.data = None
self._state = None
self._name = attributes[0]
self._icon = attributes[1]
self._unit_of_measurement = attributes[2]
self._sensor_id = attributes[3]
self._device_class = attributes[4]
@property @property
def name(self): def name(self):
"""Return the name for this sensor.""" """Return the name for this sensor."""
if self._sensor in ("sensor", "load", "switch"): sensor_key = self.entity_description.key
sensor_name = self.entity_description.name
if sensor_key in ("sensor", "load", "switch"):
return ( return (
f"{self._service_location.service_location_name} - " f"{self._service_location.service_location_name} - "
f"{self._sensor.title()} - {self._name}" f"{sensor_key.title()} - {sensor_name}"
) )
return f"{self._service_location.service_location_name} - {self._name}" return f"{self._service_location.service_location_name} - {sensor_name}"
@property @property
def icon(self): def unique_id(self):
"""Icon to use in the frontend."""
return self._icon
@property
def native_value(self):
"""Return the state of the sensor."""
return self._state
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return self._device_class
@property
def state_class(self):
"""Return the state class of this device."""
scm = STATE_CLASS_MEASUREMENT
if self._sensor in (
"power_today",
"power_current_hour",
"power_last_5_minutes",
"solar_today",
"solar_current_hour",
"alwayson_today",
"switch",
):
scm = STATE_CLASS_TOTAL_INCREASING
return scm
@property
def native_unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement
@property
def unique_id(
self,
):
"""Return the unique ID for this sensor.""" """Return the unique ID for this sensor."""
if self._sensor in ("load", "sensor", "switch"): sensor_key = self.entity_description.key
if sensor_key in ("load", "sensor", "switch"):
return ( return (
f"{self._service_location.device_serial_number}-" f"{self._service_location.device_serial_number}-"
f"{self._service_location.service_location_id}-" f"{self._service_location.service_location_id}-"
f"{self._sensor}-{self._sensor_id}" f"{sensor_key}-{self.entity_description.sensor_id}"
) )
return ( return (
f"{self._service_location.device_serial_number}-" f"{self._service_location.device_serial_number}-"
f"{self._service_location.service_location_id}-" f"{self._service_location.service_location_id}-"
f"{self._sensor}" f"{sensor_key}"
) )
@property @property
@ -373,37 +386,38 @@ class SmappeeSensor(SensorEntity):
"""Get the latest data from Smappee and update the state.""" """Get the latest data from Smappee and update the state."""
await self._smappee_base.async_update() await self._smappee_base.async_update()
if self._sensor == "total_power": sensor_key = self.entity_description.key
self._state = self._service_location.total_power if sensor_key == "total_power":
elif self._sensor == "total_reactive_power": self._attr_native_value = self._service_location.total_power
self._state = self._service_location.total_reactive_power elif sensor_key == "total_reactive_power":
elif self._sensor == "solar_power": self._attr_native_value = self._service_location.total_reactive_power
self._state = self._service_location.solar_power elif sensor_key == "solar_power":
elif self._sensor == "alwayson": self._attr_native_value = self._service_location.solar_power
self._state = self._service_location.alwayson elif sensor_key == "alwayson":
elif self._sensor in ( self._attr_native_value = self._service_location.alwayson
elif sensor_key in (
"phase_voltages_a", "phase_voltages_a",
"phase_voltages_b", "phase_voltages_b",
"phase_voltages_c", "phase_voltages_c",
): ):
phase_voltages = self._service_location.phase_voltages phase_voltages = self._service_location.phase_voltages
if phase_voltages is not None: if phase_voltages is not None:
if self._sensor == "phase_voltages_a": if sensor_key == "phase_voltages_a":
self._state = phase_voltages[0] self._attr_native_value = phase_voltages[0]
elif self._sensor == "phase_voltages_b": elif sensor_key == "phase_voltages_b":
self._state = phase_voltages[1] self._attr_native_value = phase_voltages[1]
elif self._sensor == "phase_voltages_c": elif sensor_key == "phase_voltages_c":
self._state = phase_voltages[2] self._attr_native_value = phase_voltages[2]
elif self._sensor in ("line_voltages_a", "line_voltages_b", "line_voltages_c"): elif sensor_key in ("line_voltages_a", "line_voltages_b", "line_voltages_c"):
line_voltages = self._service_location.line_voltages line_voltages = self._service_location.line_voltages
if line_voltages is not None: if line_voltages is not None:
if self._sensor == "line_voltages_a": if sensor_key == "line_voltages_a":
self._state = line_voltages[0] self._attr_native_value = line_voltages[0]
elif self._sensor == "line_voltages_b": elif sensor_key == "line_voltages_b":
self._state = line_voltages[1] self._attr_native_value = line_voltages[1]
elif self._sensor == "line_voltages_c": elif sensor_key == "line_voltages_c":
self._state = line_voltages[2] self._attr_native_value = line_voltages[2]
elif self._sensor in ( elif sensor_key in (
"power_today", "power_today",
"power_current_hour", "power_current_hour",
"power_last_5_minutes", "power_last_5_minutes",
@ -411,21 +425,23 @@ class SmappeeSensor(SensorEntity):
"solar_current_hour", "solar_current_hour",
"alwayson_today", "alwayson_today",
): ):
trend_value = self._service_location.aggregated_values.get(self._sensor) trend_value = self._service_location.aggregated_values.get(sensor_key)
self._state = round(trend_value) if trend_value is not None else None self._attr_native_value = (
elif self._sensor == "load": round(trend_value) if trend_value is not None else None
self._state = self._service_location.measurements.get( )
self._sensor_id elif sensor_key == "load":
self._attr_native_value = self._service_location.measurements.get(
self.entity_description.sensor_id
).active_total ).active_total
elif self._sensor == "sensor": elif sensor_key == "sensor":
sensor_id, channel_id = self._sensor_id.split("-") sensor_id, channel_id = self.entity_description.sensor_id.split("-")
sensor = self._service_location.sensors.get(int(sensor_id)) sensor = self._service_location.sensors.get(int(sensor_id))
for channel in sensor.channels: for channel in sensor.channels:
if channel.get("channel") == int(channel_id): if channel.get("channel") == int(channel_id):
self._state = channel.get("value_today") self._attr_native_value = channel.get("value_today")
elif self._sensor == "switch": elif sensor_key == "switch":
cons = self._service_location.actuators.get( cons = self._service_location.actuators.get(
self._sensor_id self.entity_description.sensor_id
).consumption_today ).consumption_today
if cons is not None: if cons is not None:
self._state = round(cons / 1000.0, 2) self._attr_native_value = round(cons / 1000.0, 2)