Files
homeassistant_config/config/custom_components/ecoflow/sensor.py
2024-08-26 13:38:09 +02:00

302 lines
14 KiB
Python

from datetime import timedelta
from typing import Any, Optional, Union
import reactivex.operators as ops
from homeassistant.components.sensor import (SensorDeviceClass, SensorEntity,
SensorStateClass)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (ELECTRIC_CURRENT_AMPERE,
ELECTRIC_POTENTIAL_VOLT, ENERGY_WATT_HOUR,
FREQUENCY_HERTZ, PERCENTAGE, POWER_WATT,
TEMP_CELSIUS)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.dt import utcnow
from reactivex import Observable
from . import DOMAIN, EcoFlowEntity, HassioEcoFlowClient, select_bms
from .ecoflow import (is_delta, is_delta_mini, is_delta_pro, is_power_station,
is_river)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback):
client: HassioEcoFlowClient = hass.data[DOMAIN][entry.entry_id]
entities = []
if is_power_station(client.product):
entities.extend([
CurrentEntity(client, client.inverter,
"ac_in_current", "AC input current"),
CurrentEntity(client, client.inverter,
"ac_out_current", "AC output current"),
EnergyEntity(client, client.pd, "mppt_in_energy",
"MPPT input energy"),
EnergySumEntity(client, "in_energy", [
"ac", "car", "mppt"], "Total input energy"),
EnergySumEntity(client, "out_energy", [
"ac", "car"], "Total output energy"),
FanEntity(client, client.inverter, "fan_state", "Fan"),
FrequencyEntity(client, client.inverter,
"ac_in_freq", "AC input frequency"),
FrequencyEntity(client, client.inverter,
"ac_out_freq", "AC output frequency"),
RemainEntity(client, client.pd, "remain_display", "Remain"),
LevelEntity(client, client.pd, "battery_level",
"Battery"),
VoltageEntity(client, client.inverter,
"ac_in_voltage", "AC input voltage"),
VoltageEntity(client, client.inverter,
"ac_out_voltage", "AC output voltage"),
WattsEntity(client, client.pd, "in_power", "Total input"),
WattsEntity(client, client.pd, "out_power", "Total output"),
WattsEntity(client, client.inverter,
"ac_consumption", "AC output + loss", real=True),
WattsEntity(client, client.inverter, "ac_out_power",
"AC output", real=False),
WattsEntity(client, client.pd, "usb_out1_power",
"USB-A left output"),
WattsEntity(client, client.pd, "usb_out2_power",
"USB-A right output"),
])
if is_delta(client.product):
bms = (
client.bms.pipe(select_bms(0), ops.share()),
client.bms.pipe(select_bms(1), ops.share()),
client.bms.pipe(select_bms(2), ops.share()),
)
entities.extend([
CurrentEntity(client, client.mppt, "dc_in_current",
"DC input current"),
CyclesEntity(
client, bms[0], "battery_cycles", "Main battery cycles", 0),
RemainEntity(client, client.ems,
"battery_remain_charge", "Remain charge"),
RemainEntity(client, client.ems,
"battery_remain_discharge", "Remain discharge"),
SingleLevelEntity(
client, bms[0], "battery_level_f32", "Main battery", 0),
TempEntity(client, client.inverter, "ac_out_temp",
"AC temperature"),
TempEntity(client, bms[0], "battery_temp",
"Main battery temperature", 0),
TempEntity(client, client.mppt, "dc_in_temp",
"DC input temperature"),
TempEntity(client, client.mppt, "dc24_temp",
"DC output temperature"),
TempEntity(client, client.pd, "typec_out1_temp",
"USB-C left temperature"),
TempEntity(client, client.pd, "typec_out2_temp",
"USB-C right temperature"),
VoltageEntity(client, client.mppt, "dc_in_voltage",
"DC input voltage"),
WattsEntity(client, client.inverter,
"ac_in_power", "AC input"),
WattsEntity(client, client.mppt, "dc_in_power",
"DC input", real=True),
WattsEntity(client, client.mppt,
"car_consumption", "Car output + loss", real=True),
WattsEntity(client, client.mppt,
"car_out_power", "Car output"),
])
if is_delta_mini(client.product):
entities.extend([
WattsEntity(client, client.pd,
"usbqc_out1_power", "USB-Fast output"),
WattsEntity(client, client.pd,
"typec_out1_power", "USB-C output"),
])
else:
entities.extend([
CyclesEntity(
client, bms[1], "battery_cycles", "Extra1 battery cycles", 1),
CyclesEntity(
client, bms[2], "battery_cycles", "Extra2 battery cycles", 2),
SingleLevelEntity(
client, bms[1], "battery_level_f32", "Extra1 battery", 1),
SingleLevelEntity(
client, bms[2], "battery_level_f32", "Extra2 battery", 2),
TempEntity(client, bms[1], "battery_temp",
"Extra1 battery temperature", 1),
TempEntity(client, bms[2], "battery_temp",
"Extra2 battery temperature", 2),
WattsEntity(client, client.pd, "usbqc_out1_power",
"USB-Fast left output"),
WattsEntity(client, client.pd, "usbqc_out2_power",
"USB-Fast right output"),
WattsEntity(client, client.pd, "typec_out1_power",
"USB-C left output"),
WattsEntity(client, client.pd, "typec_out2_power",
"USB-C right output"),
])
if is_delta_pro(client.product):
entities.extend([
WattsEntity(client, client.mppt,
"anderson_out_power", "Anderson output"),
])
if is_river(client.product):
extra = client.bms.pipe(select_bms(1), ops.share())
entities.extend([
CurrentEntity(client, client.inverter, "dc_in_current",
"DC input current"),
CyclesEntity(client, client.ems, "battery_cycles",
"Main battery cycles"),
CyclesEntity(client, extra, "battery_cycles",
"Extra battery cycles", 1),
SingleLevelEntity(client, client.ems, "battery_main_level",
"Main battery"),
SingleLevelEntity(
client, extra, "battery_level", "Extra battery", 1),
TempEntity(client, client.inverter, "ac_in_temp",
"AC input temperature"),
TempEntity(client, client.inverter, "ac_out_temp",
"AC output temperature"),
TempEntity(client, client.ems, "battery_main_temp",
"Main battery temperature"),
TempEntity(client, extra, "battery_temp",
"Extra battery temperature", 1),
TempEntity(client, client.pd, "car_out_temp",
"DC output temperature"),
TempEntity(client, client.pd, "typec_out1_temp",
"USB-C temperature"),
VoltageEntity(client, client.inverter, "dc_in_voltage",
"DC input voltage"),
WattsEntity(client, client.pd, "car_out_power", "Car output"),
WattsEntity(client, client.pd, "light_power", "Light output"),
WattsEntity(client, client.pd, "usbqc_out1_power",
"USB-Fast output"),
WattsEntity(client, client.pd, "typec_out1_power",
"USB-C output"),
])
async_add_entities(entities)
class BaseEntity(SensorEntity, EcoFlowEntity):
def _on_updated(self, data: dict[str, Any]):
self._attr_native_value = data[self._key]
class CurrentEntity(BaseEntity):
_attr_device_class = SensorDeviceClass.CURRENT
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_native_unit_of_measurement = ELECTRIC_CURRENT_AMPERE
_attr_state_class = SensorStateClass.MEASUREMENT
class CyclesEntity(BaseEntity):
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_icon = "mdi:battery-heart-variant"
_attr_state_class = SensorStateClass.TOTAL_INCREASING
class EnergyEntity(BaseEntity):
_attr_device_class = SensorDeviceClass.ENERGY
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_native_unit_of_measurement = ENERGY_WATT_HOUR
_attr_state_class = SensorStateClass.TOTAL_INCREASING
class EnergySumEntity(EnergyEntity):
def __init__(self, client: HassioEcoFlowClient, key: str, keys: list[str], name: str):
super().__init__(client, client.pd, key, name)
self._suffix_len = len(key) + 1
self._keys = [f"{x}_{key}" for x in keys]
def _on_updated(self, data: dict[str, Any]):
values = {key[:-self._suffix_len]: data[key]
for key in data if key in self._keys}
self._attr_extra_state_attributes = values
self._attr_native_value = sum(values.values())
class FanEntity(BaseEntity):
_attr_state_class = SensorStateClass.MEASUREMENT
@property
def icon(self):
value = self.native_value
if value is None or self.native_value <= 0:
return "mdi:fan-off"
return "mdi:fan"
class FrequencyEntity(BaseEntity):
_attr_device_class = SensorDeviceClass.FREQUENCY
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_native_unit_of_measurement = FREQUENCY_HERTZ
_attr_state_class = SensorStateClass.MEASUREMENT
class LevelEntity(BaseEntity):
_attr_device_class = SensorDeviceClass.BATTERY
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(self, client: HassioEcoFlowClient, src: Observable[dict[str, Any]], key: str, name: str, bms_id: Optional[int] = None):
super().__init__(client, src, key, name, bms_id)
self._attr_extra_state_attributes = {}
class RemainEntity(BaseEntity):
_attr_device_class = SensorDeviceClass.TIMESTAMP
_attr_entity_registry_enabled_default = False
def _on_updated(self, data: dict[str, Any]):
value: timedelta = data[self._key]
if value.total_seconds() == 8639940:
self._attr_native_value = None
else:
self._attr_native_value = utcnow() + value
class SingleLevelEntity(LevelEntity):
def _on_updated(self, data: dict[str, Any]):
super()._on_updated(data)
if "battery_capacity_remain" in data:
self._attr_extra_state_attributes["capacity_remain"] = data["battery_capacity_remain"]
if "battery_capacity_full" in data:
self._attr_extra_state_attributes["capacity_full"] = data["battery_capacity_full"]
if "battery_capacity_design" in data:
self._attr_extra_state_attributes["capacity_design"] = data["battery_capacity_design"]
class TempEntity(BaseEntity):
_attr_device_class = SensorDeviceClass.TEMPERATURE
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_native_unit_of_measurement = TEMP_CELSIUS
_attr_state_class = SensorStateClass.MEASUREMENT
class VoltageEntity(BaseEntity):
_attr_device_class = SensorDeviceClass.VOLTAGE
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_native_unit_of_measurement = ELECTRIC_POTENTIAL_VOLT
_attr_state_class = SensorStateClass.MEASUREMENT
class WattsEntity(BaseEntity):
_attr_device_class = SensorDeviceClass.POWER
_attr_native_unit_of_measurement = POWER_WATT
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(self, client: HassioEcoFlowClient, src: Observable[dict[str, Any]], key: str, name: str, real: Union[bool, int] = False):
super().__init__(client, src, key, name)
if key.endswith("_consumption"):
self._key = key[:-11] + "out_power"
self._attr_entity_category = EntityCategory.DIAGNOSTIC
self._real = real
def _on_updated(self, data: dict[str, Any]):
key = self._key[:-5]
if self._real is not False and f"{key}current" in data and f"{key}voltage" in data:
self._attr_native_value = (
data[f"{key}current"] * data[f"{key}voltage"])
if self._real is not True:
self._attr_native_value = round(
self._attr_native_value, self._real)
if self._real == 0:
self._attr_native_value = int(self._attr_native_value)
else:
super()._on_updated(data)