diff --git a/custom_components/ecoflow_cloud/__init__.py b/custom_components/ecoflow_cloud/__init__.py index c92f2a3..be6404c 100644 --- a/custom_components/ecoflow_cloud/__init__.py +++ b/custom_components/ecoflow_cloud/__init__.py @@ -20,6 +20,8 @@ Platform.SELECT, Platform.SENSOR, Platform.SWITCH, + Platform.BUTTON, + } ATTR_STATUS_SN = "SN" diff --git a/custom_components/ecoflow_cloud/button.py b/custom_components/ecoflow_cloud/button.py new file mode 100644 index 0000000..762641a --- /dev/null +++ b/custom_components/ecoflow_cloud/button.py @@ -0,0 +1,34 @@ +import logging +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import DOMAIN +from .entities import BaseButtonEntity +from .mqtt.ecoflow_mqtt import EcoflowMQTTClient + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback): + client: EcoflowMQTTClient = hass.data[DOMAIN][entry.entry_id] + + from .devices.registry import devices + async_add_entities(devices[client.device_type].buttons(client)) + + +class EnabledButtonEntity(BaseButtonEntity): + + def press(self, **kwargs: Any) -> None: + if self._command: + self.send_set_message(0, self.command_dict(0)) + +class DisabledButtonEntity(BaseButtonEntity): + + async def async_press(self, **kwargs: Any) -> None: + if self._command: + self.send_set_message(0, self.command_dict(0)) + diff --git a/custom_components/ecoflow_cloud/config/const.py b/custom_components/ecoflow_cloud/config/const.py index 6568fed..c96bfe1 100644 --- a/custom_components/ecoflow_cloud/config/const.py +++ b/custom_components/ecoflow_cloud/config/const.py @@ -26,6 +26,7 @@ class EcoflowModel(Enum): DELTA_2_MAX = 9, DELTA_MINI = 15, # productType = 15 POWERSTREAM = 51, + GLACIER = 46, DIAGNOSTIC = 99 @classmethod diff --git a/custom_components/ecoflow_cloud/devices/__init__.py b/custom_components/ecoflow_cloud/devices/__init__.py index 961100d..8040624 100644 --- a/custom_components/ecoflow_cloud/devices/__init__.py +++ b/custom_components/ecoflow_cloud/devices/__init__.py @@ -5,6 +5,7 @@ from homeassistant.components.select import SelectEntity from homeassistant.components.sensor import SensorEntity from homeassistant.components.switch import SwitchEntity +from homeassistant.components.button import ButtonEntity from homeassistant.const import Platform from ..mqtt.ecoflow_mqtt import EcoflowMQTTClient @@ -40,6 +41,10 @@ def numbers(self, client: EcoflowMQTTClient) -> list[NumberEntity]: def switches(self, client: EcoflowMQTTClient) -> list[SwitchEntity]: pass + @abstractmethod + def buttons(self, client: EcoflowMQTTClient) -> list[ButtonEntity]: + pass + @abstractmethod def selects(self, client: EcoflowMQTTClient) -> list[SelectEntity]: pass @@ -59,5 +64,8 @@ def numbers(self, client: EcoflowMQTTClient) -> list[NumberEntity]: def switches(self, client: EcoflowMQTTClient) -> list[SwitchEntity]: return [] + def buttons(self, client: EcoflowMQTTClient) -> list[ButtonEntity]: + return [] + def selects(self, client: EcoflowMQTTClient) -> list[SelectEntity]: return [] diff --git a/custom_components/ecoflow_cloud/devices/delta2.py b/custom_components/ecoflow_cloud/devices/delta2.py index 0975626..d82c1aa 100644 --- a/custom_components/ecoflow_cloud/devices/delta2.py +++ b/custom_components/ecoflow_cloud/devices/delta2.py @@ -149,6 +149,10 @@ def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: "isConfig": value, "minDsgSoc": 0}}), ] + + def buttons(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: + return [ + ] def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]: return [ diff --git a/custom_components/ecoflow_cloud/devices/delta2_max.py b/custom_components/ecoflow_cloud/devices/delta2_max.py index 0ab9cbf..40d70c3 100644 --- a/custom_components/ecoflow_cloud/devices/delta2_max.py +++ b/custom_components/ecoflow_cloud/devices/delta2_max.py @@ -136,6 +136,10 @@ def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: "moduleSn": client.device_sn, "params": {"xboost": value}}) ] + + def buttons(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: + return [ + ] def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]: return [ diff --git a/custom_components/ecoflow_cloud/devices/delta_max.py b/custom_components/ecoflow_cloud/devices/delta_max.py index 2704ae2..84f4fdb 100644 --- a/custom_components/ecoflow_cloud/devices/delta_max.py +++ b/custom_components/ecoflow_cloud/devices/delta_max.py @@ -123,6 +123,10 @@ def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"enabled": value, "id": 81 }}), ] + + def buttons(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: + return [ + ] def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]: return [ diff --git a/custom_components/ecoflow_cloud/devices/delta_mini.py b/custom_components/ecoflow_cloud/devices/delta_mini.py index 4a6568e..3de0d4b 100644 --- a/custom_components/ecoflow_cloud/devices/delta_mini.py +++ b/custom_components/ecoflow_cloud/devices/delta_mini.py @@ -116,6 +116,10 @@ def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: # EnabledEntity(client, "pd.bpPowerSoc", const.BP_ENABLED, # lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"isConfig": value}}), ] + + def buttons(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: + return [ + ] def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]: return [ diff --git a/custom_components/ecoflow_cloud/devices/delta_pro.py b/custom_components/ecoflow_cloud/devices/delta_pro.py index 5abd0b8..bf1357e 100644 --- a/custom_components/ecoflow_cloud/devices/delta_pro.py +++ b/custom_components/ecoflow_cloud/devices/delta_pro.py @@ -135,6 +135,10 @@ def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: "minDsgSoc": 0, "maxChgSoc": 0}}), ] + + def buttons(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: + return [ + ] def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]: return [ diff --git a/custom_components/ecoflow_cloud/devices/glacier.py b/custom_components/ecoflow_cloud/devices/glacier.py new file mode 100644 index 0000000..4856749 --- /dev/null +++ b/custom_components/ecoflow_cloud/devices/glacier.py @@ -0,0 +1,124 @@ +from homeassistant.const import Platform + +from . import const, BaseDevice, EntityMigration, MigrationAction +from .const import ATTR_DESIGN_CAPACITY, ATTR_FULL_CAPACITY, ATTR_REMAIN_CAPACITY, BATTERY_CHARGING_STATE +from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSwitchEntity, BaseSelectEntity, BaseButtonEntity +from ..mqtt.ecoflow_mqtt import EcoflowMQTTClient +from ..number import ChargingPowerEntity, MaxBatteryLevelEntity, MinBatteryLevelEntity, SetTempEntity +from ..select import DictSelectEntity, TimeoutDictSelectEntity +from ..sensor import LevelSensorEntity, RemainSensorEntity, SecondsRemainSensorEntity, TempSensorEntity, \ + CyclesSensorEntity, InWattsSensorEntity, OutWattsSensorEntity, VoltSensorEntity, QuotasStatusSensorEntity, \ + MilliVoltSensorEntity, InMilliVoltSensorEntity, OutMilliVoltSensorEntity, ChargingStateSensorEntity, \ + FanSensorEntity, MiscBinarySensorEntity, DecicelsiusSensorEntity, MiscSensorEntity +from ..switch import EnabledEntity +from ..button import EnabledButtonEntity + + +class Glacier(BaseDevice): + def charging_power_step(self) -> int: + return 50 + + def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]: + return [ + # Power and Battery Entities + LevelSensorEntity(client, "bms_bmsStatus.soc", const.MAIN_BATTERY_LEVEL) + .attr("bms_bmsStatus.designCap", ATTR_DESIGN_CAPACITY, 0) + .attr("bms_bmsStatus.fullCap", ATTR_FULL_CAPACITY, 0) + .attr("bms_bmsStatus.remainCap", ATTR_REMAIN_CAPACITY, 0), + + LevelSensorEntity(client, "bms_emsStatus.f32LcdSoc", const.COMBINED_BATTERY_LEVEL), + + ChargingStateSensorEntity(client, "bms_emsStatus.chgState", BATTERY_CHARGING_STATE), + + InWattsSensorEntity(client, "bms_bmsStatus.inWatts", const.TOTAL_IN_POWER), + OutWattsSensorEntity(client, "bms_bmsStatus.outWatts", const.TOTAL_OUT_POWER), + + OutWattsSensorEntity(client, "pd.motorWat", "Motor Power"), + + RemainSensorEntity(client, "bms_emsStatus.chgRemain", const.CHARGE_REMAINING_TIME), + RemainSensorEntity(client, "bms_emsStatus.dsgRemain", const.DISCHARGE_REMAINING_TIME), + + CyclesSensorEntity(client, "bms_bmsStatus.cycles", const.CYCLES), + + TempSensorEntity(client, "bms_bmsStatus.tmp", const.BATTERY_TEMP), + TempSensorEntity(client, "bms_bmsStatus.minCellTmp", const.MIN_CELL_TEMP, False), + TempSensorEntity(client, "bms_bmsStatus.maxCellTmp", const.MAX_CELL_TEMP, False), + + VoltSensorEntity(client, "bms_bmsStatus.vol", const.BATTERY_VOLT, False), + MilliVoltSensorEntity(client, "bms_bmsStatus.minCellVol", const.MIN_CELL_VOLT, False), + MilliVoltSensorEntity(client, "bms_bmsStatus.maxCellVol", const.MAX_CELL_VOLT, False), + + MiscBinarySensorEntity(client,"pd.batFlag","Battery Present"), + + MiscSensorEntity(client, "pd.xt60InState", "XT60 State"), + + #Fridge Entities + FanSensorEntity(client, "bms_emsStatus.fanLvl", "Fan Level"), + + DecicelsiusSensorEntity(client, "pd.ambientTmp", "Ambient Temperature"), + DecicelsiusSensorEntity(client, "pd.exhaustTmp", "Exhaust Temperature"), + DecicelsiusSensorEntity(client, "pd.tempWater", "Water Temperature"), + DecicelsiusSensorEntity(client, "pd.tmpL", "Left Temperature"), + DecicelsiusSensorEntity(client, "pd.tmpR", "Right Temperature"), + + MiscBinarySensorEntity(client,"pd.flagTwoZone","Dual Zone Mode"), + + SecondsRemainSensorEntity(client, "pd.iceTm", "Ice Time Remain"), + LevelSensorEntity(client, "pd.icePercent", "Ice Percentage"), + + MiscSensorEntity(client, "pd.iceMkMode", "Ice Make Mode"), + + MiscBinarySensorEntity(client,"pd.iceAlert","Ice Alert"), + MiscBinarySensorEntity(client,"pd.waterLine","Ice Water Level OK"), + + QuotasStatusSensorEntity(client) + + ] + + def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]: + return [ + SetTempEntity(client,"pd.tmpLSet", "Left Set Temperature",-25, 10, + lambda value, params: {"moduleType": 1, "operateType": "temp", + "params": {"tmpM": int(params.get("pd.tmpMSet", 0)), + "tmpL": int(value), + "tmpR": int(params.get("pd.tmpRSet", 0))}}), + + SetTempEntity(client,"pd.tmpMSet", "Combined Set Temperature",-25, 10, + lambda value, params: {"moduleType": 1, "operateType": "temp", + "params": {"tmpM": int(value), + "tmpL": int(params.get("pd.tmpLSet", 0)), + "tmpR": int(params.get("pd.tmpRSet", 0))}}), + + SetTempEntity(client,"pd.tmpRSet", "Right Set Temperature",-25, 10, + lambda value, params: {"moduleType": 1, "operateType": "temp", + "params": {"tmpM": int(params.get("pd.tmpMSet", 0)), + "tmpL": int(params.get("pd.tmpLSet", 0)), + "tmpR": int(value)}}), + + ] + + def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: + return [ + + ] + + def buttons(self, client: EcoflowMQTTClient) -> list[BaseButtonEntity]: + return [ + EnabledButtonEntity(client, "smlice", "Make Small Ice", lambda value: {"moduleType": 1, "operateType": "iceMake", "params": {"enable": 1, "iceShape": 0}}), + EnabledButtonEntity(client, "lrgice", "Make Large Ice", lambda value: {"moduleType": 1, "operateType": "iceMake", "params": {"enable": 1, "iceShape": 1}}), + EnabledButtonEntity(client, "deice", "Detach Ice", lambda value: {"moduleType": 1, "operateType": "deIce", "params": {"enable": 1}}) + + ] + + + def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]: + return [ + + ] + + def migrate(self, version) -> list[EntityMigration]: + if version == 2: + return [ + EntityMigration("pd.soc", Platform.SENSOR, MigrationAction.REMOVE), + ] + return [] diff --git a/custom_components/ecoflow_cloud/devices/powerstream.py b/custom_components/ecoflow_cloud/devices/powerstream.py index 583cb95..764de34 100644 --- a/custom_components/ecoflow_cloud/devices/powerstream.py +++ b/custom_components/ecoflow_cloud/devices/powerstream.py @@ -95,6 +95,10 @@ def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]: def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: return [] + + def buttons(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: + return [ + ] def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]: return [ diff --git a/custom_components/ecoflow_cloud/devices/registry.py b/custom_components/ecoflow_cloud/devices/registry.py index b184d51..d490326 100644 --- a/custom_components/ecoflow_cloud/devices/registry.py +++ b/custom_components/ecoflow_cloud/devices/registry.py @@ -11,6 +11,7 @@ from custom_components.ecoflow_cloud.devices.delta_max import DeltaMax from custom_components.ecoflow_cloud.devices.delta2_max import Delta2Max from custom_components.ecoflow_cloud.devices.powerstream import PowerStream +from custom_components.ecoflow_cloud.devices.glacier import Glacier devices: dict[str, BaseDevice] = { EcoflowModel.DELTA_2.name: Delta2(), @@ -24,5 +25,6 @@ EcoflowModel.DELTA_MAX.name: DeltaMax(), EcoflowModel.DELTA_2_MAX.name: Delta2Max(), EcoflowModel.POWERSTREAM.name: PowerStream(), + EcoflowModel.GLACIER.name: Glacier(), EcoflowModel.DIAGNOSTIC.name: DiagnosticDevice() } diff --git a/custom_components/ecoflow_cloud/devices/river2.py b/custom_components/ecoflow_cloud/devices/river2.py index c2bc1ec..71ba6ee 100644 --- a/custom_components/ecoflow_cloud/devices/river2.py +++ b/custom_components/ecoflow_cloud/devices/river2.py @@ -95,6 +95,10 @@ def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: lambda value: {"moduleType": 5, "operateType": "mpptCar", "params": {"enabled": value}}) ] + def buttons(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: + return [ + ] + def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]: return [ DictSelectEntity(client, "mppt.dcChgCurrent", const.DC_CHARGE_CURRENT, const.DC_CHARGE_CURRENT_OPTIONS, diff --git a/custom_components/ecoflow_cloud/devices/river2_max.py b/custom_components/ecoflow_cloud/devices/river2_max.py index e01c47a..051f710 100644 --- a/custom_components/ecoflow_cloud/devices/river2_max.py +++ b/custom_components/ecoflow_cloud/devices/river2_max.py @@ -121,6 +121,10 @@ def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: "minDsgSoc": 0, "minChgSoc": 0}}) ] + + def buttons(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: + return [ + ] def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]: return [ diff --git a/custom_components/ecoflow_cloud/devices/river2_pro.py b/custom_components/ecoflow_cloud/devices/river2_pro.py index 3b73751..499f68b 100644 --- a/custom_components/ecoflow_cloud/devices/river2_pro.py +++ b/custom_components/ecoflow_cloud/devices/river2_pro.py @@ -96,6 +96,10 @@ def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: EnabledEntity(client, "pd.carState", const.DC_ENABLED, lambda value: {"moduleType": 5, "operateType": "mpptCar", "params": {"enabled": value}}) ] + + def buttons(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: + return [ + ] def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]: return [ diff --git a/custom_components/ecoflow_cloud/devices/river_max.py b/custom_components/ecoflow_cloud/devices/river_max.py index 6fab5ae..4b5977e 100644 --- a/custom_components/ecoflow_cloud/devices/river_max.py +++ b/custom_components/ecoflow_cloud/devices/river_max.py @@ -88,6 +88,10 @@ def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: EnabledEntity(client, "inv.cfgAcXboost", const.XBOOST_ENABLED, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 66, "xboost": value}}) ] + + def buttons(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: + return [ + ] def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]: return [ diff --git a/custom_components/ecoflow_cloud/devices/river_pro.py b/custom_components/ecoflow_cloud/devices/river_pro.py index ebc12b0..f76ce01 100644 --- a/custom_components/ecoflow_cloud/devices/river_pro.py +++ b/custom_components/ecoflow_cloud/devices/river_pro.py @@ -83,6 +83,10 @@ def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: EnabledEntity(client, "inv.cfgAcEnabled", const.AC_ENABLED, None), EnabledEntity(client, "inv.cfgAcXboost", const.XBOOST_ENABLED, None) ] + + def buttons(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]: + return [ + ] def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]: return [ diff --git a/custom_components/ecoflow_cloud/entities/__init__.py b/custom_components/ecoflow_cloud/entities/__init__.py index 6f6efe8..e231fc0 100644 --- a/custom_components/ecoflow_cloud/entities/__init__.py +++ b/custom_components/ecoflow_cloud/entities/__init__.py @@ -6,6 +6,7 @@ from homeassistant.components.select import SelectEntity from homeassistant.components.sensor import SensorEntity from homeassistant.components.switch import SwitchEntity +from homeassistant.components.button import ButtonEntity from homeassistant.helpers.entity import Entity, EntityCategory from ..mqtt.ecoflow_mqtt import EcoflowMQTTClient @@ -143,3 +144,8 @@ class BaseSwitchEntity(SwitchEntity, EcoFlowBaseCommandEntity): class BaseSelectEntity(SelectEntity, EcoFlowBaseCommandEntity): pass + + +class BaseButtonEntity(ButtonEntity, EcoFlowBaseCommandEntity): + pass + diff --git a/custom_components/ecoflow_cloud/number.py b/custom_components/ecoflow_cloud/number.py index 5b6d267..3cb1a4e 100644 --- a/custom_components/ecoflow_cloud/number.py +++ b/custom_components/ecoflow_cloud/number.py @@ -3,7 +3,7 @@ from homeassistant.components.number import NumberMode from homeassistant.components.sensor import SensorDeviceClass from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE, POWER_WATT +from homeassistant.const import PERCENTAGE, POWER_WATT, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -79,3 +79,8 @@ class MinGenStartLevelEntity(LevelEntity): class MaxGenStopLevelEntity(LevelEntity): _attr_icon = "mdi:engine-off" + + +class SetTempEntity(ValueUpdateEntity): + _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS + diff --git a/custom_components/ecoflow_cloud/sensor.py b/custom_components/ecoflow_cloud/sensor.py index 715f861..c7ff888 100644 --- a/custom_components/ecoflow_cloud/sensor.py +++ b/custom_components/ecoflow_cloud/sensor.py @@ -89,6 +89,19 @@ def _update_value(self, val: Any) -> Any: ival = 0 return super()._update_value(ival) + +class SecondsRemainSensorEntity(BaseSensorEntity): + _attr_device_class = SensorDeviceClass.DURATION + _attr_native_unit_of_measurement = UnitOfTime.SECONDS + _attr_state_class = SensorStateClass.MEASUREMENT + _attr_native_value = 0 + + def _update_value(self, val: Any) -> Any: + ival = int(val) + if ival < 0 or ival > 5000: + ival = 0 + + return super()._update_value(ival) class TempSensorEntity(BaseSensorEntity):