Home Assistant Git Exporter
This commit is contained in:
102
config/custom_components/ecoflow_cloud/__init__.py
Normal file
102
config/custom_components/ecoflow_cloud/__init__.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import logging
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
|
||||
from .config.const import CONF_DEVICE_TYPE, CONF_USERNAME, CONF_PASSWORD, OPTS_POWER_STEP, OPTS_REFRESH_PERIOD_SEC, \
|
||||
DEFAULT_REFRESH_PERIOD_SEC, CONF_DEVICE_ID
|
||||
from .mqtt.ecoflow_mqtt import EcoflowMQTTClient, EcoflowAuthentication
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "ecoflow_cloud"
|
||||
CONFIG_VERSION = 3
|
||||
|
||||
_PLATFORMS = {
|
||||
Platform.NUMBER,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.BUTTON,
|
||||
|
||||
}
|
||||
|
||||
ATTR_STATUS_SN = "SN"
|
||||
ATTR_STATUS_UPDATES = "status_request_count"
|
||||
ATTR_STATUS_LAST_UPDATE = "status_last_update"
|
||||
ATTR_STATUS_DATA_LAST_UPDATE = "data_last_update"
|
||||
ATTR_STATUS_RECONNECTS = "reconnects"
|
||||
ATTR_STATUS_PHASE = "status_phase"
|
||||
|
||||
|
||||
async def async_migrate_entry(hass, config_entry: ConfigEntry):
|
||||
"""Migrate old entry."""
|
||||
if config_entry.version == 1:
|
||||
from .devices.registry import devices as device_registry
|
||||
device = device_registry[config_entry.data[CONF_DEVICE_TYPE]]
|
||||
|
||||
new_data = {**config_entry.data}
|
||||
new_options = {OPTS_POWER_STEP: device.charging_power_step(),
|
||||
OPTS_REFRESH_PERIOD_SEC: DEFAULT_REFRESH_PERIOD_SEC}
|
||||
|
||||
config_entry.version = 2
|
||||
hass.config_entries.async_update_entry(config_entry, data=new_data, options=new_options)
|
||||
_LOGGER.info("Migration to version %s successful", config_entry.version)
|
||||
|
||||
if config_entry.version < CONFIG_VERSION:
|
||||
from .devices.registry import devices as device_registry
|
||||
from .entities import EcoFlowAbstractEntity
|
||||
from .devices import EntityMigration, MigrationAction
|
||||
|
||||
device = device_registry[config_entry.data[CONF_DEVICE_TYPE]]
|
||||
device_sn = config_entry.data[CONF_DEVICE_ID]
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
for v in (config_entry.version, CONFIG_VERSION):
|
||||
migrations: list[EntityMigration] = device.migrate(v)
|
||||
for m in migrations:
|
||||
if m.action == MigrationAction.REMOVE:
|
||||
entity_id = entity_registry.async_get_entity_id(
|
||||
domain=m.domain,
|
||||
platform=DOMAIN,
|
||||
unique_id=EcoFlowAbstractEntity.gen_unique_id(sn=device_sn, key=m.key))
|
||||
|
||||
if entity_id:
|
||||
_LOGGER.info(".... removing entity_id = %s", entity_id)
|
||||
entity_registry.async_remove(entity_id)
|
||||
|
||||
config_entry.version = CONFIG_VERSION
|
||||
hass.config_entries.async_update_entry(config_entry)
|
||||
_LOGGER.info("Migration to version %s successful", config_entry.version)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
if DOMAIN not in hass.data:
|
||||
hass.data[DOMAIN] = {}
|
||||
|
||||
auth = EcoflowAuthentication(entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD])
|
||||
await hass.async_add_executor_job(auth.authorize)
|
||||
client = EcoflowMQTTClient(hass, entry, auth)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = client
|
||||
await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS)
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
if not await hass.config_entries.async_unload_platforms(entry, _PLATFORMS):
|
||||
return False
|
||||
|
||||
client: EcoflowMQTTClient = hass.data[DOMAIN].pop(entry.entry_id)
|
||||
client.stop()
|
||||
return True
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
34
config/custom_components/ecoflow_cloud/button.py
Normal file
34
config/custom_components/ecoflow_cloud/button.py
Normal file
@@ -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))
|
||||
|
||||
35
config/custom_components/ecoflow_cloud/config/const.py
Normal file
35
config/custom_components/ecoflow_cloud/config/const.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from enum import Enum
|
||||
from typing import Final
|
||||
|
||||
from homeassistant import const
|
||||
|
||||
CONF_USERNAME: Final = const.CONF_USERNAME
|
||||
CONF_PASSWORD: Final = const.CONF_PASSWORD
|
||||
CONF_DEVICE_TYPE: Final = const.CONF_TYPE
|
||||
CONF_DEVICE_NAME: Final = const.CONF_NAME
|
||||
CONF_DEVICE_ID: Final = const.CONF_DEVICE_ID
|
||||
OPTS_POWER_STEP: Final = "power_step"
|
||||
OPTS_REFRESH_PERIOD_SEC: Final = "refresh_period_sec"
|
||||
|
||||
DEFAULT_REFRESH_PERIOD_SEC: Final = 5
|
||||
|
||||
|
||||
class EcoflowModel(Enum):
|
||||
DELTA_2 = 1,
|
||||
RIVER_2 = 2,
|
||||
RIVER_2_MAX = 3,
|
||||
RIVER_2_PRO = 4,
|
||||
DELTA_PRO = 5,
|
||||
RIVER_MAX = 6,
|
||||
RIVER_PRO = 7,
|
||||
DELTA_MAX = 8, # productType = 13
|
||||
DELTA_2_MAX = 9, # productType = 81
|
||||
DELTA_MINI = 15, # productType = 15
|
||||
POWERSTREAM = 51,
|
||||
GLACIER = 46,
|
||||
WAVE_2 = 45, # productType = 45
|
||||
DIAGNOSTIC = 99
|
||||
|
||||
@classmethod
|
||||
def list(cls) -> list[str]:
|
||||
return [e.name for e in EcoflowModel]
|
||||
72
config/custom_components/ecoflow_cloud/config_flow.py
Normal file
72
config/custom_components/ecoflow_cloud/config_flow.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant import const
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigEntry, OptionsFlow
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers import selector
|
||||
|
||||
from . import DOMAIN, CONFIG_VERSION
|
||||
from .config.const import EcoflowModel, CONF_USERNAME, CONF_PASSWORD, CONF_DEVICE_TYPE, CONF_DEVICE_NAME, \
|
||||
CONF_DEVICE_ID, OPTS_POWER_STEP, OPTS_REFRESH_PERIOD_SEC, DEFAULT_REFRESH_PERIOD_SEC
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EcoflowConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
VERSION = CONFIG_VERSION
|
||||
|
||||
async def async_step_user(self, user_input: dict[str, Any] | None = None):
|
||||
errors: Dict[str, str] = {}
|
||||
if user_input is not None and not errors:
|
||||
from .devices.registry import devices
|
||||
device = devices[user_input[CONF_DEVICE_TYPE]]
|
||||
|
||||
options = {OPTS_POWER_STEP: device.charging_power_step(), OPTS_REFRESH_PERIOD_SEC: DEFAULT_REFRESH_PERIOD_SEC}
|
||||
|
||||
return self.async_create_entry(title=user_input[const.CONF_NAME], data=user_input, options=options)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
last_step=True,
|
||||
data_schema=vol.Schema({
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Required(CONF_DEVICE_TYPE): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(options=EcoflowModel.list(),
|
||||
mode=selector.SelectSelectorMode.DROPDOWN),
|
||||
),
|
||||
vol.Required(CONF_DEVICE_NAME): str,
|
||||
vol.Required(CONF_DEVICE_ID): str,
|
||||
})
|
||||
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
|
||||
return EcoflowOptionsFlow(config_entry)
|
||||
|
||||
|
||||
class EcoflowOptionsFlow(OptionsFlow):
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
last_step=True,
|
||||
data_schema=vol.Schema({
|
||||
vol.Optional(OPTS_POWER_STEP,
|
||||
default=self.config_entry.options[OPTS_POWER_STEP]): int,
|
||||
vol.Optional(OPTS_REFRESH_PERIOD_SEC,
|
||||
default=self.config_entry.options[OPTS_REFRESH_PERIOD_SEC]): int,
|
||||
})
|
||||
)
|
||||
70
config/custom_components/ecoflow_cloud/devices/__init__.py
Normal file
70
config/custom_components/ecoflow_cloud/devices/__init__.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import StrEnum
|
||||
|
||||
from homeassistant.components.number import NumberEntity
|
||||
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
|
||||
|
||||
|
||||
class MigrationAction(StrEnum):
|
||||
REMOVE = "remove"
|
||||
|
||||
|
||||
class EntityMigration:
|
||||
|
||||
def __init__(self, key: str, domain: Platform, action: MigrationAction, **kwargs):
|
||||
self.key = key
|
||||
self.domain = domain
|
||||
self.action = action
|
||||
self.args = kwargs
|
||||
|
||||
|
||||
class BaseDevice(ABC):
|
||||
|
||||
def charging_power_step(self) -> int:
|
||||
return 100
|
||||
|
||||
@abstractmethod
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[SensorEntity]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[NumberEntity]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[SwitchEntity]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[SelectEntity]:
|
||||
pass
|
||||
|
||||
def buttons(self, client: EcoflowMQTTClient) -> list[ButtonEntity]:
|
||||
return []
|
||||
|
||||
def migrate(self, version) -> list[EntityMigration]:
|
||||
return []
|
||||
|
||||
|
||||
class DiagnosticDevice(BaseDevice):
|
||||
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[SensorEntity]:
|
||||
return []
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[NumberEntity]:
|
||||
return []
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[SwitchEntity]:
|
||||
return []
|
||||
|
||||
def buttons(self, client: EcoflowMQTTClient) -> list[ButtonEntity]:
|
||||
return []
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[SelectEntity]:
|
||||
return []
|
||||
255
config/custom_components/ecoflow_cloud/devices/const.py
Normal file
255
config/custom_components/ecoflow_cloud/devices/const.py
Normal file
@@ -0,0 +1,255 @@
|
||||
DC_MODE_OPTIONS = {
|
||||
"Auto": 0,
|
||||
"Solar Recharging": 1,
|
||||
"Car Recharging": 2,
|
||||
}
|
||||
|
||||
DC_ICONS = {
|
||||
"Auto": None,
|
||||
"MPPT": "mdi:solar-power",
|
||||
"DC": "mdi:current-dc",
|
||||
}
|
||||
|
||||
SCREEN_TIMEOUT_OPTIONS = {
|
||||
"Never": 0,
|
||||
"10 sec": 10,
|
||||
"30 sec": 30,
|
||||
"1 min": 60,
|
||||
"5 min": 300,
|
||||
"30 min": 1800,
|
||||
}
|
||||
|
||||
UNIT_TIMEOUT_OPTIONS = {
|
||||
"Never": 0,
|
||||
"30 min": 30,
|
||||
"1 hr": 60,
|
||||
"2 hr": 120,
|
||||
"4 hr": 240,
|
||||
"6 hr": 360,
|
||||
"12 hr": 720,
|
||||
"24 hr": 1440
|
||||
}
|
||||
|
||||
UNIT_TIMEOUT_OPTIONS_LIMITED = {
|
||||
"Never": 0,
|
||||
"30 min": 30,
|
||||
"1 hr": 60,
|
||||
"2 hr": 120,
|
||||
"6 hr": 360,
|
||||
"12 hr": 720
|
||||
}
|
||||
|
||||
AC_TIMEOUT_OPTIONS = {
|
||||
"Never": 0,
|
||||
"30 min": 30,
|
||||
"1 hr": 60,
|
||||
"2 hr": 120,
|
||||
"4 hr": 240,
|
||||
"6 hr": 360,
|
||||
"12 hr": 720,
|
||||
"24 hr": 1440,
|
||||
}
|
||||
|
||||
AC_TIMEOUT_OPTIONS_LIMITED = {
|
||||
"Never": 0,
|
||||
"2 hr": 120,
|
||||
"4 hr": 240,
|
||||
"6 hr": 360,
|
||||
"12 hr": 720,
|
||||
"24 hr": 1440,
|
||||
}
|
||||
|
||||
DC_TIMEOUT_OPTIONS = {
|
||||
"Never": 0,
|
||||
"30 min": 30,
|
||||
"1 hr": 60,
|
||||
"2 hr": 120,
|
||||
"4 hr": 240,
|
||||
"6 hr": 360,
|
||||
"12 hr": 720,
|
||||
"24 hr": 1440,
|
||||
}
|
||||
|
||||
DC_TIMEOUT_OPTIONS_LIMITED = {
|
||||
"Never": 0,
|
||||
"2 hr": 120,
|
||||
"4 hr": 240,
|
||||
"6 hr": 360,
|
||||
"12 hr": 720,
|
||||
"24 hr": 1440,
|
||||
}
|
||||
|
||||
DC_CHARGE_CURRENT_OPTIONS = {
|
||||
"4A": 4000,
|
||||
"6A": 6000,
|
||||
"8A": 8000
|
||||
}
|
||||
|
||||
MAIN_MODE_OPTIONS = {
|
||||
"Cool": 0,
|
||||
"Heat": 1,
|
||||
"Fan": 2
|
||||
}
|
||||
|
||||
FAN_MODE_OPTIONS = {
|
||||
"Low": 0,
|
||||
"Medium": 1,
|
||||
"High": 2
|
||||
}
|
||||
|
||||
REMOTE_MODE_OPTIONS = {
|
||||
"Startup": 1,
|
||||
"Standby": 2,
|
||||
"Shutdown": 3
|
||||
}
|
||||
|
||||
POWER_SUB_MODE_OPTIONS = {
|
||||
"Max": 0,
|
||||
"Sleep": 1,
|
||||
"Eco": 2,
|
||||
"Manual": 3
|
||||
}
|
||||
|
||||
COMBINED_BATTERY_LEVEL = "Battery Level"
|
||||
COMBINED_BATTERY_LEVEL_F32 = "Battery Level (Precise)"
|
||||
BATTERY_CHARGING_STATE = "Battery Charging State"
|
||||
|
||||
ATTR_DESIGN_CAPACITY = "Design Capacity (mAh)"
|
||||
ATTR_FULL_CAPACITY = "Full Capacity (mAh)"
|
||||
ATTR_REMAIN_CAPACITY = "Remain Capacity (mAh)"
|
||||
MAIN_DESIGN_CAPACITY = "Main Design Capacity"
|
||||
MAIN_FULL_CAPACITY = "Main Full Capacity"
|
||||
MAIN_REMAIN_CAPACITY = "Main Remain Capacity"
|
||||
SLAVE_DESIGN_CAPACITY = "Slave Design Capacity"
|
||||
SLAVE_FULL_CAPACITY = "Slave Full Capacity"
|
||||
SLAVE_REMAIN_CAPACITY = "Slave Remain Capacity"
|
||||
SLAVE_N_DESIGN_CAPACITY = "Slave %i Design Capacity"
|
||||
SLAVE_N_FULL_CAPACITY = "Slave %i Full Capacity"
|
||||
SLAVE_N_REMAIN_CAPACITY = "Slave %i Remain Capacity"
|
||||
|
||||
MAIN_BATTERY_LEVEL = "Main Battery Level"
|
||||
MAIN_BATTERY_LEVEL_F32 = "Main Battery Level (Precise)"
|
||||
MAIN_BATTERY_CURRENT = "Main Battery Current"
|
||||
TOTAL_IN_POWER = "Total In Power"
|
||||
SOLAR_IN_POWER = "Solar In Power"
|
||||
SOLAR_1_IN_POWER = "Solar (1) In Power"
|
||||
SOLAR_2_IN_POWER = "Solar (2) In Power"
|
||||
AC_IN_POWER = "AC In Power"
|
||||
AC_IN_VOLT = "AC In Volts"
|
||||
AC_OUT_VOLT = "AC Out Volts"
|
||||
|
||||
TYPE_C_IN_POWER = "Type-C In Power"
|
||||
SOLAR_IN_CURRENT = "Solar In Current"
|
||||
SOLAR_IN_VOLTAGE = "Solar In Voltage"
|
||||
SOLAR_IN_ENERGY = "Solar In Energy"
|
||||
CHARGE_AC_ENERGY = "Battery Charge Energy from AC"
|
||||
CHARGE_DC_ENERGY = "Battery Charge Energy from DC"
|
||||
DISCHARGE_AC_ENERGY = "Battery Discharge Energy to AC"
|
||||
DISCHARGE_DC_ENERGY = "Battery Discharge Energy to DC"
|
||||
|
||||
TOTAL_OUT_POWER = "Total Out Power"
|
||||
AC_OUT_POWER = "AC Out Power"
|
||||
DC_OUT_POWER = "DC Out Power"
|
||||
DC_OUT_VOLTAGE = "DC Out Voltage"
|
||||
DC_CAR_OUT_POWER = "DC Car Out Power"
|
||||
DC_ANDERSON_OUT_POWER = "DC Anderson Out Power"
|
||||
|
||||
TYPEC_OUT_POWER = "Type-C Out Power"
|
||||
TYPEC_1_OUT_POWER = "Type-C (1) Out Power"
|
||||
TYPEC_2_OUT_POWER = "Type-C (2) Out Power"
|
||||
USB_OUT_POWER = "USB Out Power"
|
||||
USB_1_OUT_POWER = "USB (1) Out Power"
|
||||
USB_2_OUT_POWER = "USB (2) Out Power"
|
||||
USB_3_OUT_POWER = "USB (3) Out Power"
|
||||
|
||||
USB_QC_1_OUT_POWER = "USB QC (1) Out Power"
|
||||
USB_QC_2_OUT_POWER = "USB QC (2) Out Power"
|
||||
|
||||
REMAINING_TIME = "Remaining Time"
|
||||
CHARGE_REMAINING_TIME = "Charge Remaining Time"
|
||||
DISCHARGE_REMAINING_TIME = "Discharge Remaining Time"
|
||||
|
||||
CYCLES = "Cycles"
|
||||
SOH = "State of Health"
|
||||
|
||||
SLAVE_BATTERY_LEVEL = "Slave Battery Level"
|
||||
SLAVE_N_BATTERY_LEVEL = "Slave %i Battery Level"
|
||||
SLAVE_N_BATTERY_LEVEL_F32 = "Slave %i Battery Level (Precise)"
|
||||
|
||||
SLAVE_BATTERY_TEMP = "Slave Battery Temperature"
|
||||
SLAVE_N_BATTERY_TEMP = "Slave %i Battery Temperature"
|
||||
|
||||
SLAVE_MIN_CELL_TEMP = "Slave Min Cell Temperature"
|
||||
SLAVE_MAX_CELL_TEMP = "Slave Max Cell Temperature"
|
||||
|
||||
SLAVE_N_MIN_CELL_TEMP = "Slave %i Min Cell Temperature"
|
||||
SLAVE_N_MAX_CELL_TEMP = "Slave %i Max Cell Temperature"
|
||||
|
||||
SLAVE_CYCLES = "Slave Cycles"
|
||||
SLAVE_N_CYCLES = "Slave %i Cycles"
|
||||
SLAVE_SOH = "Slave State of Health"
|
||||
SLAVE_N_SOH = "Slave %i State of Health"
|
||||
|
||||
SLAVE_IN_POWER = "Slave In Power"
|
||||
SLAVE_N_IN_POWER = "Slave %i In Power"
|
||||
|
||||
SLAVE_OUT_POWER = "Slave Out Power"
|
||||
SLAVE_N_OUT_POWER = "Slave %i Out Power"
|
||||
|
||||
SLAVE_BATTERY_VOLT = "Slave Battery Volts"
|
||||
SLAVE_MIN_CELL_VOLT = "Slave Min Cell Volts"
|
||||
SLAVE_MAX_CELL_VOLT = "Slave Max Cell Volts"
|
||||
|
||||
SLAVE_N_BATTERY_VOLT = "Slave %i Battery Volts"
|
||||
SLAVE_N_MIN_CELL_VOLT = "Slave %i Min Cell Volts"
|
||||
SLAVE_N_MAX_CELL_VOLT = "Slave %i Max Cell Volts"
|
||||
SLAVE_N_BATTERY_CURRENT = "Slave %i Battery Current"
|
||||
|
||||
MAX_CHARGE_LEVEL = "Max Charge Level"
|
||||
MIN_DISCHARGE_LEVEL = "Min Discharge Level"
|
||||
BACKUP_RESERVE_LEVEL = "Backup Reserve Level"
|
||||
AC_CHARGING_POWER = "AC Charging Power"
|
||||
SCREEN_TIMEOUT = "Screen Timeout"
|
||||
UNIT_TIMEOUT = "Unit Timeout"
|
||||
AC_TIMEOUT = "AC Timeout"
|
||||
DC_TIMEOUT = "DC (12V) Timeout"
|
||||
DC_CHARGE_CURRENT = "DC (12V) Charge Current"
|
||||
GEN_AUTO_START_LEVEL = "Generator Auto Start Level"
|
||||
GEN_AUTO_STOP_LEVEL = "Generator Auto Stop Level"
|
||||
|
||||
BEEPER = "Beeper"
|
||||
USB_ENABLED = "USB Enabled"
|
||||
AC_ENABLED = "AC Enabled"
|
||||
DC_ENABLED = "DC (12V) Enabled"
|
||||
XBOOST_ENABLED = "X-Boost Enabled"
|
||||
AC_ALWAYS_ENABLED = "AC Always On"
|
||||
PV_PRIO = "Prio Solar Charging"
|
||||
BP_ENABLED = "Backup Reserve Enabled"
|
||||
AUTO_FAN_SPEED = "Auto Fan Speed"
|
||||
AC_SLOW_CHARGE = "AC Slow Charging"
|
||||
|
||||
DC_MODE = "DC Mode"
|
||||
|
||||
BATTERY_TEMP = "Battery Temperature"
|
||||
MIN_CELL_TEMP = "Min Cell Temperature"
|
||||
MAX_CELL_TEMP = "Max Cell Temperature"
|
||||
INV_IN_TEMP = "Inverter Inside Temperature"
|
||||
INV_OUT_TEMP = "Inverter Outside Temperature"
|
||||
DC_CAR_OUT_TEMP = "DC Temperature"
|
||||
USB_C_TEMP = "USB C Temperature"
|
||||
ATTR_MIN_CELL_TEMP = MIN_CELL_TEMP
|
||||
ATTR_MAX_CELL_TEMP = MAX_CELL_TEMP
|
||||
|
||||
BATTERY_VOLT = "Battery Volts"
|
||||
MIN_CELL_VOLT = "Min Cell Volts"
|
||||
MAX_CELL_VOLT = "Max Cell Volts"
|
||||
ATTR_MIN_CELL_VOLT = MIN_CELL_VOLT
|
||||
ATTR_MAX_CELL_VOLT = MAX_CELL_VOLT
|
||||
|
||||
BATTERY_AMP = "Battery Current"
|
||||
SLAVE_BATTERY_AMP = "Slave Battery Current"
|
||||
|
||||
FAN_MODE = "Wind speed"
|
||||
MAIN_MODE = "Main mode"
|
||||
REMOTE_MODE = "Remote startup/shutdown"
|
||||
POWER_SUB_MODE = "Sub-mode"
|
||||
200
config/custom_components/ecoflow_cloud/devices/delta2.py
Normal file
200
config/custom_components/ecoflow_cloud/devices/delta2.py
Normal file
@@ -0,0 +1,200 @@
|
||||
from homeassistant.const import Platform
|
||||
|
||||
from . import const, BaseDevice, EntityMigration, MigrationAction
|
||||
from .. import EcoflowMQTTClient
|
||||
from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSwitchEntity, BaseSelectEntity
|
||||
from ..number import ChargingPowerEntity, MinBatteryLevelEntity, MaxBatteryLevelEntity, \
|
||||
MaxGenStopLevelEntity, MinGenStartLevelEntity, BatteryBackupLevel
|
||||
from ..select import DictSelectEntity, TimeoutDictSelectEntity
|
||||
from ..sensor import LevelSensorEntity, RemainSensorEntity, TempSensorEntity, CyclesSensorEntity, \
|
||||
InWattsSensorEntity, OutWattsSensorEntity, QuotasStatusSensorEntity, MilliVoltSensorEntity, InMilliVoltSensorEntity, \
|
||||
OutMilliVoltSensorEntity, CapacitySensorEntity
|
||||
from ..switch import BeeperEntity, EnabledEntity
|
||||
|
||||
|
||||
class Delta2(BaseDevice):
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]:
|
||||
return [
|
||||
LevelSensorEntity(client, "bms_bmsStatus.soc", const.MAIN_BATTERY_LEVEL)
|
||||
.attr("bms_bmsStatus.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bms_bmsStatus.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bms_bmsStatus.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.designCap", const.MAIN_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.fullCap", const.MAIN_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.remainCap", const.MAIN_REMAIN_CAPACITY, False),
|
||||
|
||||
LevelSensorEntity(client, "bms_bmsStatus.soh", const.SOH),
|
||||
|
||||
LevelSensorEntity(client, "bms_emsStatus.lcdShowSoc", const.COMBINED_BATTERY_LEVEL),
|
||||
InWattsSensorEntity(client, "pd.wattsInSum", const.TOTAL_IN_POWER),
|
||||
OutWattsSensorEntity(client, "pd.wattsOutSum", const.TOTAL_OUT_POWER),
|
||||
|
||||
InWattsSensorEntity(client, "inv.inputWatts", const.AC_IN_POWER),
|
||||
OutWattsSensorEntity(client, "inv.outputWatts", const.AC_OUT_POWER),
|
||||
|
||||
InMilliVoltSensorEntity(client, "inv.acInVol", const.AC_IN_VOLT),
|
||||
OutMilliVoltSensorEntity(client, "inv.invOutVol", const.AC_OUT_VOLT),
|
||||
|
||||
InWattsSensorEntity(client, "mppt.inWatts", const.SOLAR_IN_POWER),
|
||||
|
||||
|
||||
|
||||
# OutWattsSensorEntity(client, "pd.carWatts", const.DC_OUT_POWER),
|
||||
# the same value as pd.carWatts
|
||||
OutWattsSensorEntity(client, "mppt.outWatts", const.DC_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.typec1Watts", const.TYPEC_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.typec2Watts", const.TYPEC_2_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.usb1Watts", const.USB_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.usb2Watts", const.USB_2_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.qcUsb1Watts", const.USB_QC_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.qcUsb2Watts", const.USB_QC_2_OUT_POWER),
|
||||
|
||||
RemainSensorEntity(client, "bms_emsStatus.chgRemainTime", const.CHARGE_REMAINING_TIME),
|
||||
RemainSensorEntity(client, "bms_emsStatus.dsgRemainTime", const.DISCHARGE_REMAINING_TIME),
|
||||
|
||||
TempSensorEntity(client, "inv.outTemp", "Inv Out Temperature"),
|
||||
CyclesSensorEntity(client, "bms_bmsStatus.cycles", const.CYCLES),
|
||||
|
||||
TempSensorEntity(client, "bms_bmsStatus.temp", const.BATTERY_TEMP)
|
||||
.attr("bms_bmsStatus.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bms_bmsStatus.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bms_bmsStatus.minCellTemp", const.MIN_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "bms_bmsStatus.maxCellTemp", const.MAX_CELL_TEMP, False),
|
||||
|
||||
MilliVoltSensorEntity(client, "bms_bmsStatus.vol", const.BATTERY_VOLT, False)
|
||||
.attr("bms_bmsStatus.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bms_bmsStatus.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bms_bmsStatus.minCellVol", const.MIN_CELL_VOLT, False),
|
||||
MilliVoltSensorEntity(client, "bms_bmsStatus.maxCellVol", const.MAX_CELL_VOLT, False),
|
||||
|
||||
# Optional Slave Battery
|
||||
LevelSensorEntity(client, "bms_slave.soc", const.SLAVE_BATTERY_LEVEL, False, True)
|
||||
.attr("bms_slave.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bms_slave.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bms_slave.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bms_slave.designCap", const.SLAVE_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_slave.fullCap", const.SLAVE_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_slave.remainCap", const.SLAVE_REMAIN_CAPACITY, False),
|
||||
|
||||
LevelSensorEntity(client, "bms_slave.soh", const.SLAVE_SOH),
|
||||
TempSensorEntity(client, "bms_slave.temp", const.SLAVE_BATTERY_TEMP, False, True)
|
||||
.attr("bms_slave.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bms_slave.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bms_slave.minCellTemp", const.SLAVE_MIN_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "bms_slave.maxCellTemp", const.SLAVE_MAX_CELL_TEMP, False),
|
||||
|
||||
MilliVoltSensorEntity(client, "bms_slave.vol", const.SLAVE_BATTERY_VOLT, False)
|
||||
.attr("bms_slave.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bms_slave.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bms_slave.minCellVol", const.SLAVE_MIN_CELL_VOLT, False),
|
||||
MilliVoltSensorEntity(client, "bms_slave.maxCellVol", const.SLAVE_MAX_CELL_VOLT, False),
|
||||
|
||||
CyclesSensorEntity(client, "bms_slave.cycles", const.SLAVE_CYCLES, False, True),
|
||||
InWattsSensorEntity(client, "bms_slave.inputWatts", const.SLAVE_IN_POWER, False, True),
|
||||
OutWattsSensorEntity(client, "bms_slave.outputWatts", const.SLAVE_OUT_POWER, False, True),
|
||||
QuotasStatusSensorEntity(client),
|
||||
|
||||
]
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]:
|
||||
return [
|
||||
MaxBatteryLevelEntity(client, "bms_emsStatus.maxChargeSoc", const.MAX_CHARGE_LEVEL, 50, 100,
|
||||
lambda value: {"moduleType": 2, "operateType": "upsConfig",
|
||||
"params": {"maxChgSoc": int(value)}}),
|
||||
|
||||
MinBatteryLevelEntity(client, "bms_emsStatus.minDsgSoc", const.MIN_DISCHARGE_LEVEL, 0, 30,
|
||||
lambda value: {"moduleType": 2, "operateType": "dsgCfg",
|
||||
"params": {"minDsgSoc": int(value)}}),
|
||||
|
||||
BatteryBackupLevel(client, "pd.bpPowerSoc", const.BACKUP_RESERVE_LEVEL, 5, 100,
|
||||
"bms_emsStatus.minDsgSoc", "bms_emsStatus.maxChargeSoc",
|
||||
lambda value: {"moduleType": 1, "operateType": "watthConfig",
|
||||
"params": {"isConfig": 1, "bpPowerSoc": int(value), "minDsgSoc": 0,
|
||||
"minChgSoc": 0}}),
|
||||
|
||||
MinGenStartLevelEntity(client, "bms_emsStatus.minOpenOilEb", const.GEN_AUTO_START_LEVEL, 0, 30,
|
||||
lambda value: {"moduleType": 2, "operateType": "openOilSoc",
|
||||
"params": {"openOilSoc": value}}),
|
||||
|
||||
MaxGenStopLevelEntity(client, "bms_emsStatus.maxCloseOilEb", const.GEN_AUTO_STOP_LEVEL, 50, 100,
|
||||
lambda value: {"moduleType": 2, "operateType": "closeOilSoc",
|
||||
"params": {"closeOilSoc": value}}),
|
||||
|
||||
ChargingPowerEntity(client, "mppt.cfgChgWatts", const.AC_CHARGING_POWER, 200, 1200,
|
||||
lambda value: {"moduleType": 5, "operateType": "acChgCfg",
|
||||
"params": {"chgWatts": int(value), "chgPauseFlag": 255}})
|
||||
|
||||
]
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]:
|
||||
return [
|
||||
BeeperEntity(client, "mppt.beepState", const.BEEPER,
|
||||
lambda value: {"moduleType": 5, "operateType": "quietMode", "params": {"enabled": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.dcOutState", const.USB_ENABLED,
|
||||
lambda value: {"moduleType": 1, "operateType": "dcOutCfg", "params": {"enabled": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.acAutoOutConfig", const.AC_ALWAYS_ENABLED,
|
||||
lambda value, params: {"moduleType": 1, "operateType": "acAutoOutConfig",
|
||||
"params": {"acAutoOutConfig": value,
|
||||
"minAcOutSoc": int(params.get("bms_emsStatus.minDsgSoc", 0)) + 5}}),
|
||||
|
||||
EnabledEntity(client, "pd.pvChgPrioSet", const.PV_PRIO,
|
||||
lambda value: {"moduleType": 1, "operateType": "pvChangePrio",
|
||||
"params": {"pvChangeSet": value}}),
|
||||
|
||||
EnabledEntity(client, "mppt.cfgAcEnabled", const.AC_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "acOutCfg",
|
||||
"params": {"enabled": value, "out_voltage": -1, "out_freq": 255,
|
||||
"xboost": 255}}),
|
||||
|
||||
EnabledEntity(client, "mppt.cfgAcXboost", const.XBOOST_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "acOutCfg",
|
||||
"params": {"enabled": 255, "out_voltage": -1, "out_freq": 255,
|
||||
"xboost": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.carState", const.DC_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "mpptCar", "params": {"enabled": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.bpPowerSoc", const.BP_ENABLED,
|
||||
lambda value: {"moduleType": 1,
|
||||
"operateType": "watthConfig",
|
||||
"params": {"bpPowerSoc": value,
|
||||
"minChgSoc": 0,
|
||||
"isConfig": value,
|
||||
"minDsgSoc": 0}}),
|
||||
]
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]:
|
||||
return [
|
||||
DictSelectEntity(client, "mppt.dcChgCurrent", const.DC_CHARGE_CURRENT, const.DC_CHARGE_CURRENT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "dcChgCfg",
|
||||
"params": {"dcChgCfg": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "pd.lcdOffSec", const.SCREEN_TIMEOUT, const.SCREEN_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 1, "operateType": "lcdCfg",
|
||||
"params": {"brighLevel": 255, "delayOff": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "pd.standbyMin", const.UNIT_TIMEOUT, const.UNIT_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 1, "operateType": "standbyTime",
|
||||
"params": {"standbyMin": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "mppt.acStandbyMins", const.AC_TIMEOUT, const.AC_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "standbyTime",
|
||||
"params": {"standbyMins": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "mppt.carStandbyMin", const.DC_TIMEOUT, const.DC_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "carStandby",
|
||||
"params": {"standbyMins": value}})
|
||||
|
||||
]
|
||||
|
||||
def migrate(self, version) -> list[EntityMigration]:
|
||||
if version == 2:
|
||||
return [
|
||||
EntityMigration("pd.soc", Platform.SENSOR, MigrationAction.REMOVE)
|
||||
]
|
||||
return []
|
||||
229
config/custom_components/ecoflow_cloud/devices/delta2_max.py
Normal file
229
config/custom_components/ecoflow_cloud/devices/delta2_max.py
Normal file
@@ -0,0 +1,229 @@
|
||||
from homeassistant.const import Platform
|
||||
|
||||
from . import const, BaseDevice, EntityMigration, MigrationAction
|
||||
from .. import EcoflowMQTTClient
|
||||
from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSwitchEntity, BaseSelectEntity
|
||||
from ..number import ChargingPowerEntity, MinBatteryLevelEntity, MaxBatteryLevelEntity, \
|
||||
MaxGenStopLevelEntity, MinGenStartLevelEntity, BatteryBackupLevel
|
||||
from ..select import TimeoutDictSelectEntity
|
||||
from ..sensor import LevelSensorEntity, RemainSensorEntity, TempSensorEntity, CyclesSensorEntity, \
|
||||
InWattsSensorEntity, OutWattsSensorEntity, StatusSensorEntity, MilliVoltSensorEntity, \
|
||||
InMilliVoltSensorEntity, OutMilliVoltSensorEntity, CapacitySensorEntity
|
||||
from ..switch import BeeperEntity, EnabledEntity
|
||||
|
||||
|
||||
class Delta2Max(BaseDevice):
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]:
|
||||
return [
|
||||
LevelSensorEntity(client, "bms_bmsStatus.soc", const.MAIN_BATTERY_LEVEL)
|
||||
.attr("bms_bmsStatus.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bms_bmsStatus.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bms_bmsStatus.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.designCap", const.MAIN_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.fullCap", const.MAIN_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.remainCap", const.MAIN_REMAIN_CAPACITY, False),
|
||||
|
||||
LevelSensorEntity(client, "bms_bmsStatus.soh", const.SOH),
|
||||
|
||||
LevelSensorEntity(client, "bms_emsStatus.lcdShowSoc", const.COMBINED_BATTERY_LEVEL),
|
||||
|
||||
InWattsSensorEntity(client, "pd.wattsInSum", const.TOTAL_IN_POWER),
|
||||
OutWattsSensorEntity(client, "pd.wattsOutSum", const.TOTAL_OUT_POWER),
|
||||
|
||||
InWattsSensorEntity(client, "inv.inputWatts", const.AC_IN_POWER),
|
||||
OutWattsSensorEntity(client, "inv.outputWatts", const.AC_OUT_POWER),
|
||||
|
||||
InMilliVoltSensorEntity(client, "inv.acInVol", const.AC_IN_VOLT),
|
||||
OutMilliVoltSensorEntity(client, "inv.invOutVol", const.AC_OUT_VOLT),
|
||||
|
||||
InWattsSensorEntity(client, "mppt.inWatts", const.SOLAR_1_IN_POWER),
|
||||
InWattsSensorEntity(client, "mppt.pv2InWatts", const.SOLAR_2_IN_POWER),
|
||||
|
||||
# OutWattsSensorEntity(client, "pd.carWatts", const.DC_OUT_POWER),
|
||||
# the same value as pd.carWatts
|
||||
OutWattsSensorEntity(client, "mppt.outWatts", const.DC_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.typec1Watts", const.TYPEC_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.typec2Watts", const.TYPEC_2_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.usb1Watts", const.USB_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.usb2Watts", const.USB_2_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.qcUsb1Watts", const.USB_QC_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.qcUsb2Watts", const.USB_QC_2_OUT_POWER),
|
||||
|
||||
RemainSensorEntity(client, "bms_emsStatus.chgRemainTime", const.CHARGE_REMAINING_TIME),
|
||||
RemainSensorEntity(client, "bms_emsStatus.dsgRemainTime", const.DISCHARGE_REMAINING_TIME),
|
||||
|
||||
TempSensorEntity(client, "inv.outTemp", "Inv Out Temperature"),
|
||||
CyclesSensorEntity(client, "bms_bmsStatus.cycles", const.CYCLES),
|
||||
|
||||
TempSensorEntity(client, "bms_bmsStatus.temp", const.BATTERY_TEMP)
|
||||
.attr("bms_bmsStatus.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bms_bmsStatus.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bms_bmsStatus.minCellTemp", const.MIN_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "bms_bmsStatus.maxCellTemp", const.MAX_CELL_TEMP, False),
|
||||
|
||||
MilliVoltSensorEntity(client, "bms_bmsStatus.vol", const.BATTERY_VOLT, False)
|
||||
.attr("bms_bmsStatus.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bms_bmsStatus.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bms_bmsStatus.minCellVol", const.MIN_CELL_VOLT, False),
|
||||
MilliVoltSensorEntity(client, "bms_bmsStatus.maxCellVol", const.MAX_CELL_VOLT, False),
|
||||
|
||||
# Optional Slave 1 Battery
|
||||
LevelSensorEntity(client, "bms_slave_bmsSlaveStatus_1.soc", const.SLAVE_N_BATTERY_LEVEL % 1, False, True)
|
||||
.attr("bms_slave_bmsSlaveStatus_1.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bms_slave_bmsSlaveStatus_1.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bms_slave_bmsSlaveStatus_1.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bms_slave_bmsSlaveStatus_1.designCap", const.SLAVE_N_DESIGN_CAPACITY % 1, False),
|
||||
CapacitySensorEntity(client, "bms_slave_bmsSlaveStatus_1.fullCap", const.SLAVE_N_FULL_CAPACITY % 1, False),
|
||||
CapacitySensorEntity(client, "bms_slave_bmsSlaveStatus_1.remainCap", const.SLAVE_N_REMAIN_CAPACITY % 1, False),
|
||||
|
||||
TempSensorEntity(client, "bms_slave_bmsSlaveStatus_1.temp", const.SLAVE_N_BATTERY_TEMP % 1, False, True)
|
||||
.attr("bms_slave_bmsSlaveStatus_1.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bms_slave_bmsSlaveStatus_1.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bms_slave_bmsSlaveStatus_1.minCellTemp", const.SLAVE_N_MIN_CELL_TEMP % 1, False),
|
||||
TempSensorEntity(client, "bms_slave_bmsSlaveStatus_1.maxCellTemp", const.SLAVE_N_MAX_CELL_TEMP % 1, False),
|
||||
|
||||
MilliVoltSensorEntity(client, "bms_slave_bmsSlaveStatus_1.vol", const.SLAVE_N_BATTERY_VOLT % 1, False)
|
||||
.attr("bms_slave_bmsSlaveStatus_1.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bms_slave_bmsSlaveStatus_1.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bms_slave_bmsSlaveStatus_1.minCellVol", const.SLAVE_N_MIN_CELL_VOLT % 1, False),
|
||||
MilliVoltSensorEntity(client, "bms_slave_bmsSlaveStatus_1.maxCellVol", const.SLAVE_N_MAX_CELL_VOLT % 1, False),
|
||||
|
||||
CyclesSensorEntity(client, "bms_slave_bmsSlaveStatus_1.cycles", const.SLAVE_N_CYCLES % 1, False, True),
|
||||
LevelSensorEntity(client, "bms_slave_bmsSlaveStatus_1.soh", const.SLAVE_N_SOH % 1, False,True),
|
||||
InWattsSensorEntity(client, "bms_slave_bmsSlaveStatus_1.inputWatts", const.SLAVE_N_IN_POWER % 1, False, True),
|
||||
OutWattsSensorEntity(client, "bms_slave_bmsSlaveStatus_1.outputWatts", const.SLAVE_N_OUT_POWER % 1, False, True),
|
||||
|
||||
# Optional Slave 2 Battery
|
||||
LevelSensorEntity(client, "bms_slave_bmsSlaveStatus_2.soc", const.SLAVE_N_BATTERY_LEVEL % 2, False, True)
|
||||
.attr("bms_slave_bmsSlaveStatus_2.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bms_slave_bmsSlaveStatus_2.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bms_slave_bmsSlaveStatus_2.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bms_slave_bmsSlaveStatus_2.designCap", const.SLAVE_N_DESIGN_CAPACITY % 2, False),
|
||||
CapacitySensorEntity(client, "bms_slave_bmsSlaveStatus_2.fullCap", const.SLAVE_N_FULL_CAPACITY % 2, False),
|
||||
CapacitySensorEntity(client, "bms_slave_bmsSlaveStatus_2.remainCap", const.SLAVE_N_REMAIN_CAPACITY % 2, False),
|
||||
|
||||
TempSensorEntity(client, "bms_slave_bmsSlaveStatus_2.temp", const.SLAVE_N_BATTERY_TEMP % 2, False, True)
|
||||
.attr("bms_slave_bmsSlaveStatus_2.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bms_slave_bmsSlaveStatus_2.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bms_slave_bmsSlaveStatus_2.minCellTemp", const.SLAVE_N_MIN_CELL_TEMP % 2, False),
|
||||
TempSensorEntity(client, "bms_slave_bmsSlaveStatus_2.maxCellTemp", const.SLAVE_N_MAX_CELL_TEMP % 2, False),
|
||||
|
||||
MilliVoltSensorEntity(client, "bms_slave_bmsSlaveStatus_2.vol", const.SLAVE_N_BATTERY_VOLT % 2, False)
|
||||
.attr("bms_slave_bmsSlaveStatus_2.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bms_slave_bmsSlaveStatus_2.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bms_slave_bmsSlaveStatus_2.minCellVol", const.SLAVE_N_MIN_CELL_VOLT % 2, False),
|
||||
MilliVoltSensorEntity(client, "bms_slave_bmsSlaveStatus_2.maxCellVol", const.SLAVE_N_MAX_CELL_VOLT % 2, False),
|
||||
|
||||
CyclesSensorEntity(client, "bms_slave_bmsSlaveStatus_2.cycles", const.SLAVE_N_CYCLES % 2, False, True),
|
||||
LevelSensorEntity(client, "bms_slave_bmsSlaveStatus_2.soh", const.SLAVE_N_SOH % 2, False, True),
|
||||
InWattsSensorEntity(client, "bms_slave_bmsSlaveStatus_2.inputWatts", const.SLAVE_N_IN_POWER % 2, False, True),
|
||||
OutWattsSensorEntity(client, "bms_slave_bmsSlaveStatus_2.outputWatts", const.SLAVE_N_OUT_POWER % 2, False, True),
|
||||
|
||||
StatusSensorEntity(client),
|
||||
|
||||
]
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]:
|
||||
return [
|
||||
MaxBatteryLevelEntity(client, "bms_emsStatus.maxChargeSoc", const.MAX_CHARGE_LEVEL, 50, 100,
|
||||
lambda value: {"moduleType": 2, "operateType": "upsConfig",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"maxChgSoc": int(value)}}),
|
||||
|
||||
MinBatteryLevelEntity(client, "bms_emsStatus.minDsgSoc", const.MIN_DISCHARGE_LEVEL, 0, 30,
|
||||
lambda value: {"moduleType": 2, "operateType": "dsgCfg",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"minDsgSoc": int(value)}}),
|
||||
|
||||
BatteryBackupLevel(client, "pd.bpPowerSoc", const.BACKUP_RESERVE_LEVEL, 5, 100,
|
||||
"bms_emsStatus.minDsgSoc", "bms_emsStatus.maxChargeSoc",
|
||||
lambda value: {"moduleType": 1, "operateType": "watthConfig",
|
||||
"params": {"isConfig": 1, "bpPowerSoc": int(value), "minDsgSoc": 0,
|
||||
"minChgSoc": 0}}),
|
||||
|
||||
MinGenStartLevelEntity(client, "bms_emsStatus.minOpenOilEbSoc", const.GEN_AUTO_START_LEVEL, 0, 30,
|
||||
lambda value: {"moduleType": 2, "operateType": "openOilSoc",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"openOilSoc": value}}),
|
||||
|
||||
MaxGenStopLevelEntity(client, "bms_emsStatus.maxCloseOilEbSoc", const.GEN_AUTO_STOP_LEVEL, 50, 100,
|
||||
lambda value: {"moduleType": 2, "operateType": "closeOilSoc",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"closeOilSoc": value}}),
|
||||
|
||||
ChargingPowerEntity(client, "inv.SlowChgWatts", const.AC_CHARGING_POWER, 200, 2400,
|
||||
lambda value: {"moduleType": 3, "operateType": "acChgCfg",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"slowChgWatts": int(value), "fastChgWatts": 255,
|
||||
"chgPauseFlag": 0}})
|
||||
|
||||
]
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]:
|
||||
return [
|
||||
BeeperEntity(client, "pd.beepMode", const.BEEPER,
|
||||
lambda value: {"moduleType": 1, "operateType": "quietCfg",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"enabled": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.dcOutState", const.USB_ENABLED,
|
||||
lambda value: {"moduleType": 1, "operateType": "dcOutCfg",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"enabled": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.newAcAutoOnCfg", const.AC_ALWAYS_ENABLED,
|
||||
lambda value: {"moduleType": 1, "operateType": "newAcAutoOnCfg",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"enabled": value, "minAcSoc": 5}}),
|
||||
|
||||
EnabledEntity(client, "inv.cfgAcEnabled", const.AC_ENABLED,
|
||||
lambda value: {"moduleType": 3, "operateType": "acOutCfg",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"enabled": value, "out_voltage": -1,
|
||||
"out_freq": 255, "xboost": 255}}),
|
||||
|
||||
EnabledEntity(client, "inv.cfgAcXboost", const.XBOOST_ENABLED,
|
||||
lambda value: {"moduleType": 3, "operateType": "acOutCfg",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"xboost": value}}),
|
||||
EnabledEntity(client, "pd.carState", const.DC_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "mpptCar",
|
||||
"params": {"enabled": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.bpPowerSoc", const.BP_ENABLED,
|
||||
lambda value: {"moduleType": 1,
|
||||
"operateType": "watthConfig",
|
||||
"params": {"bpPowerSoc": value,
|
||||
"minChgSoc": 0,
|
||||
"isConfig": value,
|
||||
"minDsgSoc": 0}}),
|
||||
]
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]:
|
||||
return [
|
||||
TimeoutDictSelectEntity(client, "pd.lcdOffSec", const.SCREEN_TIMEOUT, const.SCREEN_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 1, "operateType": "lcdCfg",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"brighLevel": 255, "delayOff": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "inv.standbyMin", const.UNIT_TIMEOUT, const.UNIT_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 1, "operateType": "standbyTime",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"standbyMin": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "mppt.carStandbyMin", const.AC_TIMEOUT, const.AC_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "standbyTime",
|
||||
"moduleSn": client.device_sn,
|
||||
"params": {"standbyMins": value}}),
|
||||
]
|
||||
|
||||
def migrate(self, version) -> list[EntityMigration]:
|
||||
if version == 2:
|
||||
return [
|
||||
EntityMigration("pd.soc", Platform.SENSOR, MigrationAction.REMOVE),
|
||||
EntityMigration("bms_emsStatus.f32LcdShowSoc", Platform.SENSOR, MigrationAction.REMOVE)
|
||||
]
|
||||
return []
|
||||
160
config/custom_components/ecoflow_cloud/devices/delta_max.py
Normal file
160
config/custom_components/ecoflow_cloud/devices/delta_max.py
Normal file
@@ -0,0 +1,160 @@
|
||||
from homeassistant.const import Platform
|
||||
|
||||
from . import const, BaseDevice, EntityMigration, MigrationAction
|
||||
from .. import EcoflowMQTTClient
|
||||
from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSwitchEntity, BaseSelectEntity
|
||||
from ..number import ChargingPowerEntity, MinBatteryLevelEntity, MaxBatteryLevelEntity, \
|
||||
MaxGenStopLevelEntity, MinGenStartLevelEntity
|
||||
from ..sensor import LevelSensorEntity, RemainSensorEntity, TempSensorEntity, CyclesSensorEntity, \
|
||||
InWattsSensorEntity, OutWattsSensorEntity, StatusSensorEntity, MilliVoltSensorEntity, \
|
||||
InMilliVoltSensorEntity, OutMilliVoltSensorEntity, CapacitySensorEntity, InWattsSolarSensorEntity, \
|
||||
OutWattsDcSensorEntity
|
||||
from ..switch import BeeperEntity, EnabledEntity
|
||||
|
||||
|
||||
class DeltaMax(BaseDevice):
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]:
|
||||
return [
|
||||
LevelSensorEntity(client, "bmsMaster.soc", const.MAIN_BATTERY_LEVEL)
|
||||
.attr("bmsMaster.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bmsMaster.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bmsMaster.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bmsMaster.designCap", const.MAIN_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsMaster.fullCap", const.MAIN_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsMaster.remainCap", const.MAIN_REMAIN_CAPACITY, False),
|
||||
|
||||
LevelSensorEntity(client, "ems.lcdShowSoc", const.COMBINED_BATTERY_LEVEL),
|
||||
InWattsSensorEntity(client, "pd.wattsInSum", const.TOTAL_IN_POWER),
|
||||
OutWattsSensorEntity(client, "pd.wattsOutSum", const.TOTAL_OUT_POWER),
|
||||
|
||||
InWattsSensorEntity(client, "inv.inputWatts", const.AC_IN_POWER),
|
||||
OutWattsSensorEntity(client, "inv.outputWatts", const.AC_OUT_POWER),
|
||||
|
||||
InMilliVoltSensorEntity(client, "inv.acInVol", const.AC_IN_VOLT),
|
||||
OutMilliVoltSensorEntity(client, "inv.invOutVol", const.AC_OUT_VOLT),
|
||||
|
||||
InWattsSolarSensorEntity(client, "mppt.inWatts", const.SOLAR_IN_POWER),
|
||||
OutWattsDcSensorEntity(client, "mppt.outWatts", const.DC_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.typec1Watts", const.TYPEC_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.typec2Watts", const.TYPEC_2_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.usb1Watts", const.USB_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.usb2Watts", const.USB_2_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.qcUsb1Watts", const.USB_QC_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.qcUsb2Watts", const.USB_QC_2_OUT_POWER),
|
||||
|
||||
RemainSensorEntity(client, "ems.chgRemainTime", const.CHARGE_REMAINING_TIME),
|
||||
RemainSensorEntity(client, "ems.dsgRemainTime", const.DISCHARGE_REMAINING_TIME),
|
||||
|
||||
TempSensorEntity(client, "inv.outTemp", "Inv Out Temperature"),
|
||||
CyclesSensorEntity(client, "bmsMaster.cycles", const.CYCLES),
|
||||
|
||||
TempSensorEntity(client, "bmsMaster.temp", const.BATTERY_TEMP)
|
||||
.attr("bmsMaster.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bmsMaster.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bmsMaster.minCellTemp", const.MIN_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "bmsMaster.maxCellTemp", const.MAX_CELL_TEMP, False),
|
||||
|
||||
MilliVoltSensorEntity(client, "bmsMaster.vol", const.BATTERY_VOLT, False)
|
||||
.attr("bmsMaster.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bmsMaster.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bmsMaster.minCellVol", const.MIN_CELL_VOLT, False),
|
||||
MilliVoltSensorEntity(client, "bmsMaster.maxCellVol", const.MAX_CELL_VOLT, False),
|
||||
|
||||
# Optional Slave Battery
|
||||
#LevelSensorEntity(client, "bms_slave.soc", const.SLAVE_BATTERY_LEVEL, False, True),
|
||||
#TempSensorEntity(client, "bms_slave.temp", const.SLAVE_BATTERY_TEMP, False, True),
|
||||
#TempSensorEntity(client, "bms_slave.minCellTemp", const.SLAVE_MIN_CELL_TEMP, False),
|
||||
#TempSensorEntity(client, "bms_slave.maxCellTemp", const.SLAVE_MAX_CELL_TEMP, False),
|
||||
|
||||
#VoltSensorEntity(client, "bms_slave.vol", const.SLAVE_BATTERY_VOLT, False),
|
||||
#VoltSensorEntity(client, "bms_slave.minCellVol", const.SLAVE_MIN_CELL_VOLT, False),
|
||||
#VoltSensorEntity(client, "bms_slave.maxCellVol", const.SLAVE_MAX_CELL_VOLT, False),
|
||||
|
||||
#CyclesSensorEntity(client, "bms_slave.cycles", const.SLAVE_CYCLES, False, True),
|
||||
#InWattsSensorEntity(client, "bms_slave.inputWatts", const.SLAVE_IN_POWER, False, True),
|
||||
#OutWattsSensorEntity(client, "bms_slave.outputWatts", const.SLAVE_OUT_POWER, False, True)
|
||||
StatusSensorEntity(client),
|
||||
]
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]:
|
||||
return [
|
||||
MaxBatteryLevelEntity(client, "ems.maxChargeSoc", const.MAX_CHARGE_LEVEL, 50, 100,
|
||||
lambda value: {"moduleType": 2, "operateType": "TCP",
|
||||
"params": {"id": 49, "maxChgSoc": value}}),
|
||||
|
||||
MinBatteryLevelEntity(client, "ems.minDsgSoc", const.MIN_DISCHARGE_LEVEL, 0, 30,
|
||||
lambda value: {"moduleType": 2, "operateType": "TCP",
|
||||
"params": {"id": 51, "minDsgSoc": value}}),
|
||||
|
||||
MinGenStartLevelEntity(client, "ems.minOpenOilEbSoc", const.GEN_AUTO_START_LEVEL, 0, 30,
|
||||
lambda value: {"moduleType": 2, "operateType": "TCP",
|
||||
"params": {"id": 52, "openOilSoc": value}}),
|
||||
|
||||
MaxGenStopLevelEntity(client, "ems.maxCloseOilEbSoc", const.GEN_AUTO_STOP_LEVEL, 50, 100,
|
||||
lambda value: {"moduleType": 2, "operateType": "TCP",
|
||||
"params": {"id": 53, "closeOilSoc": value}}),
|
||||
|
||||
ChargingPowerEntity(client, "inv.cfgFastChgWatt", const.AC_CHARGING_POWER, 200, 2000,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"slowChgPower": value, "id": 69}}),
|
||||
|
||||
]
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]:
|
||||
return [
|
||||
BeeperEntity(client, "pd.beepState", const.BEEPER,
|
||||
lambda value: {"moduleType": 5, "operateType": "TCP", "params": {"id": 38, "enabled": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.dcOutState", const.USB_ENABLED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"enabled": value, "id": 34 }}),
|
||||
|
||||
EnabledEntity(client, "pd.acAutoOnCfg", const.AC_ALWAYS_ENABLED,
|
||||
lambda value: {"moduleType": 1, "operateType": "acAutoOn", "params": {"cfg": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.pvChgPrioSet", const.PV_PRIO,
|
||||
lambda value: {"moduleType": 1, "operateType": "pvChangePrio", "params": {"pvChangeSet": value}}),
|
||||
|
||||
EnabledEntity(client, "inv.cfgAcEnabled", const.AC_ENABLED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"enabled": value, "id": 66 }}),
|
||||
|
||||
EnabledEntity(client, "inv.cfgAcXboost", const.XBOOST_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "TCP", "params": {"id": 66, "xboost": value}}),
|
||||
|
||||
EnabledEntity(client, "mppt.carState", const.DC_ENABLED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"enabled": value, "id": 81 }}),
|
||||
|
||||
]
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]:
|
||||
return [
|
||||
#DictSelectEntity(client, "mppt.cfgDcChgCurrent", const.DC_CHARGE_CURRENT, const.DC_CHARGE_CURRENT_OPTIONS,
|
||||
# lambda value: {"moduleType": 5, "operateType": "dcChgCfg",
|
||||
# "params": {"dcChgCfg": value}}),
|
||||
|
||||
#TimeoutDictSelectEntity(client, "pd.lcdOffSec", const.SCREEN_TIMEOUT, const.SCREEN_TIMEOUT_OPTIONS,
|
||||
# lambda value: {"moduleType": 1, "operateType": "lcdCfg",
|
||||
# "params": {"brighLevel": 255, "delayOff": value}}),
|
||||
|
||||
#TimeoutDictSelectEntity(client, "inv.cfgStandbyMin", const.UNIT_TIMEOUT, const.UNIT_TIMEOUT_OPTIONS,
|
||||
# lambda value: {"moduleType": 1, "operateType": "standbyTime",
|
||||
# "params": {"standbyMin": value}}),
|
||||
|
||||
#TimeoutDictSelectEntity(client, "mppt.acStandbyMins", const.AC_TIMEOUT, const.AC_TIMEOUT_OPTIONS,
|
||||
# lambda value: {"moduleType": 5, "operateType": "standbyTime",
|
||||
# "params": {"standbyMins": value}}),
|
||||
|
||||
#TimeoutDictSelectEntity(client, "mppt.carStandbyMin", const.DC_TIMEOUT, const.DC_TIMEOUT_OPTIONS,
|
||||
# lambda value: {"moduleType": 5, "operateType": "carStandby",
|
||||
# "params": {"standbyMins": value}})
|
||||
|
||||
]
|
||||
|
||||
def migrate(self, version) -> list[EntityMigration]:
|
||||
if version == 2:
|
||||
return [
|
||||
EntityMigration("pd.soc", Platform.SENSOR, MigrationAction.REMOVE),
|
||||
]
|
||||
return []
|
||||
147
config/custom_components/ecoflow_cloud/devices/delta_mini.py
Normal file
147
config/custom_components/ecoflow_cloud/devices/delta_mini.py
Normal file
@@ -0,0 +1,147 @@
|
||||
from homeassistant.const import Platform
|
||||
|
||||
from . import const, BaseDevice, EntityMigration, MigrationAction
|
||||
from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSwitchEntity, BaseSelectEntity
|
||||
from ..mqtt.ecoflow_mqtt import EcoflowMQTTClient
|
||||
from ..number import ChargingPowerEntity, MaxBatteryLevelEntity, MinBatteryLevelEntity
|
||||
from ..select import DictSelectEntity, TimeoutDictSelectEntity
|
||||
from ..sensor import LevelSensorEntity, WattsSensorEntity, RemainSensorEntity, TempSensorEntity, \
|
||||
CyclesSensorEntity, InWattsSensorEntity, OutWattsSensorEntity, OutWattsDcSensorEntity, InWattsSolarSensorEntity, \
|
||||
StatusSensorEntity, InEnergySensorEntity, OutEnergySensorEntity, MilliVoltSensorEntity, InMilliVoltSensorEntity, \
|
||||
OutMilliVoltSensorEntity, CapacitySensorEntity
|
||||
from ..switch import BeeperEntity, EnabledEntity
|
||||
|
||||
|
||||
class DeltaMini(BaseDevice):
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]:
|
||||
return [
|
||||
LevelSensorEntity(client, "bmsMaster.soc", const.MAIN_BATTERY_LEVEL)
|
||||
.attr("bmsMaster.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bmsMaster.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bmsMaster.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bmsMaster.designCap", const.MAIN_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsMaster.fullCap", const.MAIN_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsMaster.remainCap", const.MAIN_REMAIN_CAPACITY, False),
|
||||
|
||||
LevelSensorEntity(client, "bmsMaster.soh", const.SOH),
|
||||
|
||||
LevelSensorEntity(client, "ems.lcdShowSoc", const.COMBINED_BATTERY_LEVEL),
|
||||
|
||||
WattsSensorEntity(client, "pd.wattsInSum", const.TOTAL_IN_POWER),
|
||||
WattsSensorEntity(client, "pd.wattsOutSum", const.TOTAL_OUT_POWER),
|
||||
|
||||
InWattsSensorEntity(client, "inv.inputWatts", const.AC_IN_POWER),
|
||||
OutWattsSensorEntity(client, "inv.outputWatts", const.AC_OUT_POWER),
|
||||
|
||||
InMilliVoltSensorEntity(client, "inv.acInVol", const.AC_IN_VOLT),
|
||||
OutMilliVoltSensorEntity(client, "inv.invOutVol", const.AC_OUT_VOLT),
|
||||
|
||||
InWattsSolarSensorEntity(client, "mppt.inWatts", const.SOLAR_IN_POWER),
|
||||
|
||||
OutWattsDcSensorEntity(client, "mppt.outWatts", const.DC_OUT_POWER),
|
||||
|
||||
OutWattsDcSensorEntity(client, "mppt.carOutWatts", const.DC_CAR_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "mppt.dcdc12vWatts", const.DC_ANDERSON_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.typec1Watts", const.TYPEC_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.typec2Watts", const.TYPEC_2_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.usb1Watts", const.USB_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.usb2Watts", const.USB_2_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.qcUsb1Watts", const.USB_QC_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.qcUsb2Watts", const.USB_QC_2_OUT_POWER),
|
||||
|
||||
RemainSensorEntity(client, "ems.chgRemainTime", const.CHARGE_REMAINING_TIME),
|
||||
RemainSensorEntity(client, "ems.dsgRemainTime", const.DISCHARGE_REMAINING_TIME),
|
||||
CyclesSensorEntity(client, "bmsMaster.cycles", const.CYCLES),
|
||||
|
||||
TempSensorEntity(client, "bmsMaster.temp", const.BATTERY_TEMP, False)
|
||||
.attr("bmsMaster.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bmsMaster.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
|
||||
MilliVoltSensorEntity(client, "bmsMaster.vol", const.BATTERY_VOLT, False)
|
||||
.attr("bmsMaster.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bmsMaster.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
|
||||
# https://github.com/tolwi/hassio-ecoflow-cloud/discussions/87
|
||||
InEnergySensorEntity(client, "pd.chgSunPower", const.SOLAR_IN_ENERGY),
|
||||
InEnergySensorEntity(client, "pd.chgPowerAc", const.CHARGE_AC_ENERGY),
|
||||
InEnergySensorEntity(client, "pd.chgPowerDc", const.CHARGE_DC_ENERGY),
|
||||
OutEnergySensorEntity(client, "pd.dsgPowerAc", const.DISCHARGE_AC_ENERGY),
|
||||
OutEnergySensorEntity(client, "pd.dsgPowerDc", const.DISCHARGE_DC_ENERGY),
|
||||
|
||||
StatusSensorEntity(client),
|
||||
]
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]:
|
||||
return [
|
||||
MaxBatteryLevelEntity(client, "ems.maxChargeSoc", const.MAX_CHARGE_LEVEL, 50, 100,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"id": 49, "maxChgSoc": value}}),
|
||||
MinBatteryLevelEntity(client, "ems.minDsgSoc", const.MIN_DISCHARGE_LEVEL, 0, 30,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"id": 51, "minDsgSoc": value}}),
|
||||
# MaxBatteryLevelEntity(client, "pd.bpPowerSoc", const.BACKUP_RESERVE_LEVEL, 5, 100,
|
||||
# lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
# "params": {"isConfig": 1, "bpPowerSoc": int(value), "minDsgSoc": 0, "maxChgSoc": 0, "id": 94}}),
|
||||
# MinGenStartLevelEntity(client, "ems.minOpenOilEbSoc", const.GEN_AUTO_START_LEVEL, 0, 30,
|
||||
# lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
# "params": {"openOilSoc": value, "id": 52}}),
|
||||
#
|
||||
# MaxGenStopLevelEntity(client, "ems.maxCloseOilEbSoc", const.GEN_AUTO_STOP_LEVEL, 50, 100,
|
||||
# lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
# "params": {"closeOilSoc": value, "id": 53}}),
|
||||
|
||||
ChargingPowerEntity(client, "inv.cfgSlowChgWatts", const.AC_CHARGING_POWER, 200, 900,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"slowChgPower": value, "id": 69}}),
|
||||
|
||||
]
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]:
|
||||
return [
|
||||
BeeperEntity(client, "mppt.beepState", const.BEEPER,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 38, "enabled": value}}),
|
||||
EnabledEntity(client, "mppt.carState", const.DC_ENABLED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"id": 81, "enabled": value}}),
|
||||
EnabledEntity(client, "inv.cfgAcEnabled", const.AC_ENABLED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"id": 66, "enabled": value}}),
|
||||
|
||||
EnabledEntity(client, "inv.cfgAcXboost", const.XBOOST_ENABLED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 66, "xboost": value}}),
|
||||
|
||||
# EnabledEntity(client, "inv.acPassByAutoEn", const.AC_ALWAYS_ENABLED,
|
||||
# lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 84, "enabled": value}}),
|
||||
# EnabledEntity(client, "pd.bpPowerSoc", const.BP_ENABLED,
|
||||
# lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"isConfig": value}}),
|
||||
]
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]:
|
||||
return [
|
||||
DictSelectEntity(client, "mppt.cfgDcChgCurrent", const.DC_CHARGE_CURRENT, const.DC_CHARGE_CURRENT_OPTIONS,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"currMa": value, "id": 71}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "pd.lcdOffSec", const.SCREEN_TIMEOUT, const.SCREEN_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"lcdTime": value, "id": 39}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "pd.standByMode", const.UNIT_TIMEOUT, const.UNIT_TIMEOUT_OPTIONS_LIMITED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"standByMode": value, "id": 33}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "inv.cfgStandbyMin", const.AC_TIMEOUT, const.AC_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"standByMins": value, "id": 153}}),
|
||||
|
||||
]
|
||||
|
||||
def migrate(self, version) -> list[EntityMigration]:
|
||||
if version == 2:
|
||||
return [
|
||||
EntityMigration("pd.soc", Platform.SENSOR, MigrationAction.REMOVE),
|
||||
]
|
||||
return []
|
||||
212
config/custom_components/ecoflow_cloud/devices/delta_pro.py
Normal file
212
config/custom_components/ecoflow_cloud/devices/delta_pro.py
Normal file
@@ -0,0 +1,212 @@
|
||||
from homeassistant.const import Platform
|
||||
|
||||
from . import const, BaseDevice, EntityMigration, MigrationAction
|
||||
from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSwitchEntity, BaseSelectEntity
|
||||
from ..mqtt.ecoflow_mqtt import EcoflowMQTTClient
|
||||
from ..number import ChargingPowerEntity, MaxBatteryLevelEntity, MinBatteryLevelEntity, MinGenStartLevelEntity, \
|
||||
MaxGenStopLevelEntity
|
||||
from ..select import DictSelectEntity, TimeoutDictSelectEntity
|
||||
from ..sensor import LevelSensorEntity, WattsSensorEntity, RemainSensorEntity, TempSensorEntity, \
|
||||
CyclesSensorEntity, InWattsSensorEntity, OutWattsSensorEntity, OutWattsDcSensorEntity, VoltSensorEntity, \
|
||||
InWattsSolarSensorEntity, InVoltSolarSensorEntity, InAmpSolarSensorEntity, OutVoltDcSensorEntity, \
|
||||
StatusSensorEntity, InEnergySensorEntity, OutEnergySensorEntity, MilliVoltSensorEntity, InMilliVoltSensorEntity, \
|
||||
OutMilliVoltSensorEntity, AmpSensorEntity, CapacitySensorEntity
|
||||
from ..switch import BeeperEntity, EnabledEntity
|
||||
|
||||
|
||||
class DeltaPro(BaseDevice):
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]:
|
||||
return [
|
||||
LevelSensorEntity(client, "bmsMaster.soc", const.MAIN_BATTERY_LEVEL)
|
||||
.attr("bmsMaster.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bmsMaster.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bmsMaster.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
LevelSensorEntity(client, "bmsMaster.f32ShowSoc", const.MAIN_BATTERY_LEVEL_F32, False)
|
||||
.attr("bmsMaster.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bmsMaster.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bmsMaster.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bmsMaster.designCap", const.MAIN_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsMaster.fullCap", const.MAIN_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsMaster.remainCap", const.MAIN_REMAIN_CAPACITY, False),
|
||||
LevelSensorEntity(client, "bmsMaster.soh", const.SOH),
|
||||
|
||||
LevelSensorEntity(client, "ems.lcdShowSoc", const.COMBINED_BATTERY_LEVEL),
|
||||
LevelSensorEntity(client, "ems.f32LcdShowSoc", const.COMBINED_BATTERY_LEVEL_F32, False),
|
||||
WattsSensorEntity(client, "pd.wattsInSum", const.TOTAL_IN_POWER),
|
||||
WattsSensorEntity(client, "pd.wattsOutSum", const.TOTAL_OUT_POWER),
|
||||
AmpSensorEntity(client, "bmsMaster.amp", const.MAIN_BATTERY_CURRENT),
|
||||
|
||||
InWattsSensorEntity(client, "inv.inputWatts", const.AC_IN_POWER),
|
||||
OutWattsSensorEntity(client, "inv.outputWatts", const.AC_OUT_POWER),
|
||||
|
||||
InMilliVoltSensorEntity(client, "inv.acInVol", const.AC_IN_VOLT),
|
||||
OutMilliVoltSensorEntity(client, "inv.invOutVol", const.AC_OUT_VOLT),
|
||||
|
||||
InWattsSolarSensorEntity(client, "mppt.inWatts", const.SOLAR_IN_POWER),
|
||||
InVoltSolarSensorEntity(client, "mppt.inVol", const.SOLAR_IN_VOLTAGE),
|
||||
InAmpSolarSensorEntity(client, "mppt.inAmp", const.SOLAR_IN_CURRENT),
|
||||
|
||||
OutWattsDcSensorEntity(client, "mppt.outWatts", const.DC_OUT_POWER),
|
||||
OutVoltDcSensorEntity(client, "mppt.outVol", const.DC_OUT_VOLTAGE),
|
||||
|
||||
OutWattsSensorEntity(client, "mppt.carOutWatts", const.DC_CAR_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "mppt.dcdc12vWatts", const.DC_ANDERSON_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.typec1Watts", const.TYPEC_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.typec2Watts", const.TYPEC_2_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.usb1Watts", const.USB_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.usb2Watts", const.USB_2_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.qcUsb1Watts", const.USB_QC_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.qcUsb2Watts", const.USB_QC_2_OUT_POWER),
|
||||
|
||||
RemainSensorEntity(client, "ems.chgRemainTime", const.CHARGE_REMAINING_TIME),
|
||||
RemainSensorEntity(client, "ems.dsgRemainTime", const.DISCHARGE_REMAINING_TIME),
|
||||
CyclesSensorEntity(client, "bmsMaster.cycles", const.CYCLES),
|
||||
|
||||
TempSensorEntity(client, "bmsMaster.temp", const.BATTERY_TEMP)
|
||||
.attr("bmsMaster.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bmsMaster.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bmsMaster.minCellTemp", const.MIN_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "bmsMaster.maxCellTemp", const.MAX_CELL_TEMP, False),
|
||||
|
||||
MilliVoltSensorEntity(client, "bmsMaster.vol", const.BATTERY_VOLT, False)
|
||||
.attr("bmsMaster.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bmsMaster.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bmsMaster.minCellVol", const.MIN_CELL_VOLT, False),
|
||||
MilliVoltSensorEntity(client, "bmsMaster.maxCellVol", const.MAX_CELL_VOLT, False),
|
||||
|
||||
# https://github.com/tolwi/hassio-ecoflow-cloud/discussions/87
|
||||
InEnergySensorEntity(client, "pd.chgSunPower", const.SOLAR_IN_ENERGY),
|
||||
InEnergySensorEntity(client, "pd.chgPowerAc", const.CHARGE_AC_ENERGY),
|
||||
InEnergySensorEntity(client, "pd.chgPowerDc", const.CHARGE_DC_ENERGY),
|
||||
OutEnergySensorEntity(client, "pd.dsgPowerAc", const.DISCHARGE_AC_ENERGY),
|
||||
OutEnergySensorEntity(client, "pd.dsgPowerDc", const.DISCHARGE_DC_ENERGY),
|
||||
|
||||
# Optional Slave Batteries
|
||||
LevelSensorEntity(client, "bmsSlave1.soc", const.SLAVE_N_BATTERY_LEVEL % 1, False, True)
|
||||
.attr("bmsSlave1.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bmsSlave1.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bmsSlave1.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
LevelSensorEntity(client, "bmsSlave1.f32ShowSoc", const.SLAVE_N_BATTERY_LEVEL_F32 % 1, False, False)
|
||||
.attr("bmsSlave1.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bmsSlave1.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bmsSlave1.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bmsSlave1.designCap", const.SLAVE_N_DESIGN_CAPACITY % 1, False),
|
||||
CapacitySensorEntity(client, "bmsSlave1.fullCap", const.SLAVE_N_FULL_CAPACITY % 1, False),
|
||||
CapacitySensorEntity(client, "bmsSlave1.remainCap", const.SLAVE_N_REMAIN_CAPACITY % 1, False),
|
||||
LevelSensorEntity(client, "bmsSlave1.soh", const.SLAVE_N_SOH % 1),
|
||||
|
||||
TempSensorEntity(client, "bmsSlave1.temp", const.SLAVE_N_BATTERY_TEMP % 1, False, True)
|
||||
.attr("bmsSlave1.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bmsSlave1.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
WattsSensorEntity(client, "bmsSlave1.inputWatts", const.SLAVE_N_IN_POWER % 1, False, True),
|
||||
WattsSensorEntity(client, "bmsSlave1.outputWatts", const.SLAVE_N_OUT_POWER % 1, False, True),
|
||||
|
||||
LevelSensorEntity(client, "bmsSlave2.soc", const.SLAVE_N_BATTERY_LEVEL % 2, False, True)
|
||||
.attr("bmsSlave2.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bmsSlave2.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bmsSlave2.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
LevelSensorEntity(client, "bmsSlave2.f32ShowSoc", const.SLAVE_N_BATTERY_LEVEL_F32 % 2, False, False)
|
||||
.attr("bmsSlave2.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bmsSlave2.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bmsSlave2.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bmsSlave2.designCap", const.SLAVE_N_DESIGN_CAPACITY % 2, False),
|
||||
CapacitySensorEntity(client, "bmsSlave2.fullCap", const.SLAVE_N_FULL_CAPACITY % 2, False),
|
||||
CapacitySensorEntity(client, "bmsSlave2.remainCap", const.SLAVE_N_REMAIN_CAPACITY % 2, False),
|
||||
LevelSensorEntity(client, "bmsSlave2.soh", const.SLAVE_N_SOH % 2),
|
||||
MilliVoltSensorEntity(client, "bmsSlave1.vol", const.SLAVE_N_BATTERY_VOLT % 1, False),
|
||||
MilliVoltSensorEntity(client, "bmsSlave1.minCellVol", const.SLAVE_N_MIN_CELL_VOLT % 1, False),
|
||||
MilliVoltSensorEntity(client, "bmsSlave1.maxCellVol", const.SLAVE_N_MAX_CELL_VOLT % 1, False),
|
||||
AmpSensorEntity(client, "bmsSlave1.amp", const.SLAVE_N_BATTERY_CURRENT % 1, False),
|
||||
MilliVoltSensorEntity(client, "bmsSlave2.vol", const.SLAVE_N_BATTERY_VOLT % 2, False),
|
||||
MilliVoltSensorEntity(client, "bmsSlave2.minCellVol", const.SLAVE_N_MIN_CELL_VOLT % 2, False),
|
||||
MilliVoltSensorEntity(client, "bmsSlave2.maxCellVol", const.SLAVE_N_MAX_CELL_VOLT % 2, False),
|
||||
AmpSensorEntity(client, "bmsSlave2.amp", const.SLAVE_N_BATTERY_CURRENT % 2, False),
|
||||
TempSensorEntity(client, "bmsSlave2.temp", const.SLAVE_N_BATTERY_TEMP % 2, False, True)
|
||||
.attr("bmsSlave2.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bmsSlave2.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
WattsSensorEntity(client, "bmsSlave2.inputWatts", const.SLAVE_N_IN_POWER % 2, False, True),
|
||||
WattsSensorEntity(client, "bmsSlave2.outputWatts", const.SLAVE_N_OUT_POWER % 2, False, True),
|
||||
CyclesSensorEntity(client, "bmsSlave1.cycles", const.SLAVE_N_CYCLES % 1, False),
|
||||
CyclesSensorEntity(client, "bmsSlave2.cycles", const.SLAVE_N_CYCLES % 2, False),
|
||||
StatusSensorEntity(client),
|
||||
]
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]:
|
||||
return [
|
||||
MaxBatteryLevelEntity(client, "ems.maxChargeSoc", const.MAX_CHARGE_LEVEL, 50, 100,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"id": 49, "maxChgSoc": value}}),
|
||||
MinBatteryLevelEntity(client, "ems.minDsgSoc", const.MIN_DISCHARGE_LEVEL, 0, 30,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"id": 51, "minDsgSoc": value}}),
|
||||
MaxBatteryLevelEntity(client, "pd.bpPowerSoc", const.BACKUP_RESERVE_LEVEL, 5, 100,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"isConfig": 1, "bpPowerSoc": int(value), "minDsgSoc": 0,
|
||||
"maxChgSoc": 0, "id": 94}}),
|
||||
MinGenStartLevelEntity(client, "ems.minOpenOilEbSoc", const.GEN_AUTO_START_LEVEL, 0, 30,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"openOilSoc": value, "id": 52}}),
|
||||
|
||||
MaxGenStopLevelEntity(client, "ems.maxCloseOilEbSoc", const.GEN_AUTO_STOP_LEVEL, 50, 100,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"closeOilSoc": value, "id": 53}}),
|
||||
|
||||
ChargingPowerEntity(client, "inv.cfgSlowChgWatts", const.AC_CHARGING_POWER, 200, 2900,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"slowChgPower": value, "id": 69}}),
|
||||
|
||||
]
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]:
|
||||
return [
|
||||
BeeperEntity(client, "pd.beepState", const.BEEPER,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 38, "enabled": value}}),
|
||||
EnabledEntity(client, "mppt.carState", const.DC_ENABLED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"id": 81, "enabled": value}}),
|
||||
EnabledEntity(client, "inv.cfgAcEnabled", const.AC_ENABLED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"id": 66, "enabled": value}}),
|
||||
|
||||
EnabledEntity(client, "inv.cfgAcXboost", const.XBOOST_ENABLED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 66, "xboost": value}}),
|
||||
EnabledEntity(client, "pd.acautooutConfig", const.AC_ALWAYS_ENABLED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"id": 95, "acautooutConfig": value}}),
|
||||
EnabledEntity(client, "pd.bppowerSoc", const.BP_ENABLED,
|
||||
lambda value, params: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"id": 94, "isConfig": value,
|
||||
"bpPowerSoc": int(params.get("pd.bppowerSoc", 0)),
|
||||
"minDsgSoc": 0,
|
||||
"maxChgSoc": 0}}),
|
||||
]
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]:
|
||||
return [
|
||||
DictSelectEntity(client, "mppt.cfgDcChgCurrent", const.DC_CHARGE_CURRENT, const.DC_CHARGE_CURRENT_OPTIONS,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"currMa": value, "id": 71}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "pd.lcdOffSec", const.SCREEN_TIMEOUT, const.SCREEN_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"lcdTime": value, "id": 39}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "pd.standByMode", const.UNIT_TIMEOUT, const.UNIT_TIMEOUT_OPTIONS_LIMITED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"standByMode": value, "id": 33}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "inv.cfgStandbyMin", const.AC_TIMEOUT, const.AC_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"standByMins": value, "id": 153}}),
|
||||
|
||||
]
|
||||
|
||||
def migrate(self, version) -> list[EntityMigration]:
|
||||
if version == 2:
|
||||
return [
|
||||
EntityMigration("pd.soc", Platform.SENSOR, MigrationAction.REMOVE),
|
||||
]
|
||||
return []
|
||||
129
config/custom_components/ecoflow_cloud/devices/glacier.py
Normal file
129
config/custom_components/ecoflow_cloud/devices/glacier.py
Normal file
@@ -0,0 +1,129 @@
|
||||
from . import const, BaseDevice
|
||||
from ..button import EnabledButtonEntity
|
||||
from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSwitchEntity, BaseSelectEntity, BaseButtonEntity
|
||||
from ..mqtt.ecoflow_mqtt import EcoflowMQTTClient
|
||||
from ..number import SetTempEntity
|
||||
from ..sensor import LevelSensorEntity, RemainSensorEntity, SecondsRemainSensorEntity, TempSensorEntity, \
|
||||
CyclesSensorEntity, InWattsSensorEntity, OutWattsSensorEntity, VoltSensorEntity, QuotasStatusSensorEntity, \
|
||||
MilliVoltSensorEntity, ChargingStateSensorEntity, \
|
||||
FanSensorEntity, MiscBinarySensorEntity, DecicelsiusSensorEntity, MiscSensorEntity, CapacitySensorEntity
|
||||
from ..switch import EnabledEntity, InvertedBeeperEntity
|
||||
|
||||
|
||||
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", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bms_bmsStatus.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bms_bmsStatus.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.designCap", const.MAIN_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.fullCap", const.MAIN_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.remainCap", const.MAIN_REMAIN_CAPACITY, False),
|
||||
|
||||
LevelSensorEntity(client, "bms_emsStatus.f32LcdSoc", const.COMBINED_BATTERY_LEVEL),
|
||||
|
||||
ChargingStateSensorEntity(client, "bms_emsStatus.chgState", const.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)
|
||||
.attr("bms_bmsStatus.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bms_bmsStatus.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
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)
|
||||
.attr("bms_bmsStatus.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bms_bmsStatus.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
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 [
|
||||
InvertedBeeperEntity(client, "pd.beepEn", const.BEEPER,
|
||||
lambda value: {"moduleType": 1, "operateType": "beepEn", "params": {"flag": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.coolMode", "Eco Mode",
|
||||
lambda value: {"moduleType": 1, "operateType": "ecoMode", "params": {"mode": value}}),
|
||||
|
||||
#power parameter is inverted for some reason
|
||||
EnabledEntity(client, "pd.pwrState", "Power",
|
||||
lambda value: {"moduleType": 1, "operateType": "powerOff", "params": {"enable": value}}),
|
||||
|
||||
]
|
||||
|
||||
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 [
|
||||
|
||||
]
|
||||
104
config/custom_components/ecoflow_cloud/devices/powerstream.py
Normal file
104
config/custom_components/ecoflow_cloud/devices/powerstream.py
Normal file
@@ -0,0 +1,104 @@
|
||||
from . import BaseDevice
|
||||
from .. import EcoflowMQTTClient
|
||||
from ..entities import (
|
||||
BaseSensorEntity, BaseNumberEntity, BaseSelectEntity, BaseSwitchEntity
|
||||
)
|
||||
from ..sensor import (
|
||||
AmpSensorEntity, CentivoltSensorEntity, DeciampSensorEntity,
|
||||
DecicelsiusSensorEntity, DecihertzSensorEntity, DeciwattsSensorEntity,
|
||||
DecivoltSensorEntity, InWattsSolarSensorEntity, LevelSensorEntity,
|
||||
MiscSensorEntity, RemainSensorEntity, StatusSensorEntity,
|
||||
)
|
||||
# from ..number import MinBatteryLevelEntity, MaxBatteryLevelEntity
|
||||
# from ..select import DictSelectEntity
|
||||
|
||||
class PowerStream(BaseDevice):
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]:
|
||||
return [
|
||||
InWattsSolarSensorEntity(client, "pv1_input_watts", "Solar 1 Watts"),
|
||||
DecivoltSensorEntity(client, "pv1_input_volt", "Solar 1 Input Potential"),
|
||||
CentivoltSensorEntity(client, "pv1_op_volt", "Solar 1 Op Potential"),
|
||||
DeciampSensorEntity(client, "pv1_input_cur", "Solar 1 Currrent"),
|
||||
DecicelsiusSensorEntity(client, "pv1_temp", "Solar 1 Temperature"),
|
||||
MiscSensorEntity(client, "pv1_relay_status", "Solar 1 Relay Status"),
|
||||
MiscSensorEntity(client, "pv1_error_code", "Solar 1 Error Code", False),
|
||||
MiscSensorEntity(client, "pv1_warning_code", "Solar 1 Warning Code", False),
|
||||
MiscSensorEntity(client, "pv1_status", "Solar 1 Status", False),
|
||||
|
||||
InWattsSolarSensorEntity(client, "pv2_input_watts", "Solar 2 Watts"),
|
||||
DecivoltSensorEntity(client, "pv2_input_volt", "Solar 2 Input Potential"),
|
||||
CentivoltSensorEntity(client, "pv2_op_volt", "Solar 2 Op Potential"),
|
||||
DeciampSensorEntity(client, "pv2_input_cur", "Solar 2 Current"),
|
||||
DecicelsiusSensorEntity(client, "pv2_temp", "Solar 2 Temperature"),
|
||||
MiscSensorEntity(client, "pv2_relay_status", "Solar 2 Relay Status"),
|
||||
MiscSensorEntity(client, "pv2_error_code", "Solar 2 Error Code", False),
|
||||
MiscSensorEntity(client, "pv2_warning_code", "Solar 2 Warning Code", False),
|
||||
MiscSensorEntity(client, "pv2_status", "Solar 2 Status", False),
|
||||
|
||||
MiscSensorEntity(client, "bp_type", "Battery Type", False),
|
||||
LevelSensorEntity(client, "bat_soc", "Battery Charge"),
|
||||
DeciwattsSensorEntity(client, "bat_input_watts", "Battery Input Watts"),
|
||||
DecivoltSensorEntity(client, "bat_input_volt", "Battery Input Potential"),
|
||||
DecivoltSensorEntity(client, "bat_op_volt", "Battery Op Potential"),
|
||||
AmpSensorEntity(client, "bat_input_cur", "Battery Input Current"),
|
||||
DecicelsiusSensorEntity(client, "bat_temp", "Battery Temperature"),
|
||||
RemainSensorEntity(client, "battery_charge_remain", "Charge Time"),
|
||||
RemainSensorEntity(client, "battery_discharge_remain", "Discharge Time"),
|
||||
MiscSensorEntity(client, "bat_error_code", "Battery Error Code", False),
|
||||
MiscSensorEntity(client, "bat_warning_code", "Battery Warning Code", False),
|
||||
MiscSensorEntity(client, "bat_status", "Battery Status", False),
|
||||
|
||||
DecivoltSensorEntity(client, "llc_input_volt", "LLC Input Potential", False),
|
||||
DecivoltSensorEntity(client, "llc_op_volt", "LLC Op Potential", False),
|
||||
MiscSensorEntity(client, "llc_error_code", "LLC Error Code", False),
|
||||
MiscSensorEntity(client, "llc_warning_code", "LLC Warning Code", False),
|
||||
MiscSensorEntity(client, "llc_status", "LLC Status", False),
|
||||
|
||||
MiscSensorEntity(client, "inv_on_off", "Inverter On/Off Status"),
|
||||
DeciwattsSensorEntity(client, "inv_output_watts", "Inverter Output Watts"),
|
||||
DecivoltSensorEntity(client, "inv_input_volt", "Inverter Output Potential", False),
|
||||
DecivoltSensorEntity(client, "inv_op_volt", "Inverter Op Potential"),
|
||||
AmpSensorEntity(client, "inv_output_cur", "Inverter Output Current"),
|
||||
AmpSensorEntity(client, "inv_dc_cur", "Inverter DC Current"),
|
||||
DecihertzSensorEntity(client, "inv_freq", "Inverter Frequency"),
|
||||
DecicelsiusSensorEntity(client, "inv_temp", "Inverter Temperature"),
|
||||
MiscSensorEntity(client, "inv_relay_status", "Inverter Relay Status"),
|
||||
MiscSensorEntity(client, "inv_error_code", "Inverter Error Code", False),
|
||||
MiscSensorEntity(client, "inv_warning_code", "Inverter Warning Code", False),
|
||||
MiscSensorEntity(client, "inv_status", "Inverter Status", False),
|
||||
|
||||
DeciwattsSensorEntity(client, "permanent_watts", "Other Loads"),
|
||||
DeciwattsSensorEntity(client, "dynamic_watts", "Smart Plug Loads"),
|
||||
DeciwattsSensorEntity(client, "rated_power", "Rated Power"),
|
||||
|
||||
MiscSensorEntity(client, "lower_limit", "Lower Battery Limit", False),
|
||||
MiscSensorEntity(client, "upper_limit", "Upper Battery Limit", False),
|
||||
MiscSensorEntity(client, "wireless_error_code", "Wireless Error Code", False),
|
||||
MiscSensorEntity(client, "wireless_warning_code", "Wireless Warning Code", False),
|
||||
MiscSensorEntity(client, "inv_brightness", "LED Brightness", False),
|
||||
MiscSensorEntity(client, "heartbeat_frequency", "Heartbeat Frequency", False),
|
||||
|
||||
StatusSensorEntity(client)
|
||||
]
|
||||
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]:
|
||||
return [
|
||||
# These will likely be some form of serialised data rather than JSON will look into it later
|
||||
# MinBatteryLevelEntity(client, "lowerLimit", "Min Disharge Level", 50, 100,
|
||||
# lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
# "params": {"id": 00, "lowerLimit": value}}),
|
||||
# MaxBatteryLevelEntity(client, "upperLimit", "Max Charge Level", 0, 30,
|
||||
# lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
# "params": {"id": 00, "upperLimit": value}}),
|
||||
]
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]:
|
||||
return []
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]:
|
||||
return [
|
||||
# DictSelectEntity(client, "supplyPriority", "Power supply mode", {"Prioritize power supply", "Prioritize power storage"},
|
||||
# lambda value: {"moduleType": 00, "operateType": "supplyPriority",
|
||||
# "params": {"supplyPriority": value}}),
|
||||
]
|
||||
32
config/custom_components/ecoflow_cloud/devices/registry.py
Normal file
32
config/custom_components/ecoflow_cloud/devices/registry.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from custom_components.ecoflow_cloud.config_flow import EcoflowModel
|
||||
from custom_components.ecoflow_cloud.devices import BaseDevice, DiagnosticDevice
|
||||
from custom_components.ecoflow_cloud.devices.delta2 import Delta2
|
||||
from custom_components.ecoflow_cloud.devices.delta_mini import DeltaMini
|
||||
from custom_components.ecoflow_cloud.devices.delta_pro import DeltaPro
|
||||
from custom_components.ecoflow_cloud.devices.river2 import River2
|
||||
from custom_components.ecoflow_cloud.devices.river2_max import River2Max
|
||||
from custom_components.ecoflow_cloud.devices.river2_pro import River2Pro
|
||||
from custom_components.ecoflow_cloud.devices.river_max import RiverMax
|
||||
from custom_components.ecoflow_cloud.devices.river_pro import RiverPro
|
||||
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
|
||||
from custom_components.ecoflow_cloud.devices.wave2 import Wave2
|
||||
|
||||
devices: dict[str, BaseDevice] = {
|
||||
EcoflowModel.DELTA_2.name: Delta2(),
|
||||
EcoflowModel.RIVER_2.name: River2(),
|
||||
EcoflowModel.RIVER_2_MAX.name: River2Max(),
|
||||
EcoflowModel.RIVER_2_PRO.name: River2Pro(),
|
||||
EcoflowModel.DELTA_PRO.name: DeltaPro(),
|
||||
EcoflowModel.RIVER_MAX.name: RiverMax(),
|
||||
EcoflowModel.RIVER_PRO.name: RiverPro(),
|
||||
EcoflowModel.DELTA_MINI.name: DeltaMini(),
|
||||
EcoflowModel.DELTA_MAX.name: DeltaMax(),
|
||||
EcoflowModel.DELTA_2_MAX.name: Delta2Max(),
|
||||
EcoflowModel.POWERSTREAM.name: PowerStream(),
|
||||
EcoflowModel.GLACIER.name: Glacier(),
|
||||
EcoflowModel.WAVE_2.name: Wave2(),
|
||||
EcoflowModel.DIAGNOSTIC.name: DiagnosticDevice()
|
||||
}
|
||||
135
config/custom_components/ecoflow_cloud/devices/river2.py
Normal file
135
config/custom_components/ecoflow_cloud/devices/river2.py
Normal file
@@ -0,0 +1,135 @@
|
||||
from homeassistant.const import Platform
|
||||
|
||||
from . import const, BaseDevice, EntityMigration, MigrationAction
|
||||
from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSwitchEntity, BaseSelectEntity
|
||||
from ..mqtt.ecoflow_mqtt import EcoflowMQTTClient
|
||||
from ..number import ChargingPowerEntity, MaxBatteryLevelEntity, MinBatteryLevelEntity
|
||||
from ..select import DictSelectEntity, TimeoutDictSelectEntity
|
||||
from ..sensor import LevelSensorEntity, RemainSensorEntity, TempSensorEntity, \
|
||||
CyclesSensorEntity, InWattsSensorEntity, OutWattsSensorEntity, VoltSensorEntity, StatusSensorEntity, \
|
||||
MilliVoltSensorEntity, InMilliVoltSensorEntity, OutMilliVoltSensorEntity, ChargingStateSensorEntity, \
|
||||
CapacitySensorEntity
|
||||
from ..switch import EnabledEntity
|
||||
|
||||
|
||||
class River2(BaseDevice):
|
||||
def charging_power_step(self) -> int:
|
||||
return 50
|
||||
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]:
|
||||
return [
|
||||
LevelSensorEntity(client, "bms_bmsStatus.soc", const.MAIN_BATTERY_LEVEL)
|
||||
.attr("bms_bmsStatus.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bms_bmsStatus.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bms_bmsStatus.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.designCap", const.MAIN_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.fullCap", const.MAIN_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.remainCap", const.MAIN_REMAIN_CAPACITY, False),
|
||||
|
||||
LevelSensorEntity(client, "bms_bmsStatus.soh", const.SOH),
|
||||
|
||||
LevelSensorEntity(client, "bms_emsStatus.lcdShowSoc", const.COMBINED_BATTERY_LEVEL),
|
||||
|
||||
ChargingStateSensorEntity(client, "bms_emsStatus.chgState", const.BATTERY_CHARGING_STATE),
|
||||
|
||||
InWattsSensorEntity(client, "pd.wattsInSum", const.TOTAL_IN_POWER),
|
||||
OutWattsSensorEntity(client, "pd.wattsOutSum", const.TOTAL_OUT_POWER),
|
||||
|
||||
InWattsSensorEntity(client, "inv.inputWatts", const.AC_IN_POWER),
|
||||
OutWattsSensorEntity(client, "inv.outputWatts", const.AC_OUT_POWER),
|
||||
|
||||
InMilliVoltSensorEntity(client, "inv.acInVol", const.AC_IN_VOLT),
|
||||
OutMilliVoltSensorEntity(client, "inv.invOutVol", const.AC_OUT_VOLT),
|
||||
|
||||
InWattsSensorEntity(client, "pd.typecChaWatts", const.TYPE_C_IN_POWER),
|
||||
InWattsSensorEntity(client, "mppt.inWatts", const.SOLAR_IN_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.carWatts", const.DC_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.typec1Watts", const.TYPEC_1_OUT_POWER),
|
||||
|
||||
# both USB-A Ports (the small RIVER 2 has only two) are being summarized under "pd.usb1Watts" - https://github.com/tolwi/hassio-ecoflow-cloud/issues/12#issuecomment-1432837393
|
||||
OutWattsSensorEntity(client, "pd.usb1Watts", const.USB_OUT_POWER),
|
||||
|
||||
RemainSensorEntity(client, "bms_emsStatus.chgRemainTime", const.CHARGE_REMAINING_TIME),
|
||||
RemainSensorEntity(client, "bms_emsStatus.dsgRemainTime", const.DISCHARGE_REMAINING_TIME),
|
||||
|
||||
TempSensorEntity(client, "inv.outTemp", "Inv Out Temperature"),
|
||||
CyclesSensorEntity(client, "bms_bmsStatus.cycles", const.CYCLES),
|
||||
|
||||
TempSensorEntity(client, "bms_bmsStatus.temp", const.BATTERY_TEMP)
|
||||
.attr("bms_bmsStatus.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bms_bmsStatus.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bms_bmsStatus.minCellTemp", const.MIN_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "bms_bmsStatus.maxCellTemp", const.MAX_CELL_TEMP, False),
|
||||
|
||||
VoltSensorEntity(client, "bms_bmsStatus.vol", const.BATTERY_VOLT, False)
|
||||
.attr("bms_bmsStatus.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bms_bmsStatus.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bms_bmsStatus.minCellVol", const.MIN_CELL_VOLT, False),
|
||||
MilliVoltSensorEntity(client, "bms_bmsStatus.maxCellVol", const.MAX_CELL_VOLT, False),
|
||||
|
||||
# FanSensorEntity(client, "bms_emsStatus.fanLevel", "Fan Level"),
|
||||
StatusSensorEntity(client),
|
||||
|
||||
]
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]:
|
||||
return [
|
||||
MaxBatteryLevelEntity(client, "bms_emsStatus.maxChargeSoc", const.MAX_CHARGE_LEVEL, 50, 100,
|
||||
lambda value: {"moduleType": 2, "operateType": "upsConfig",
|
||||
"params": {"maxChgSoc": int(value)}}),
|
||||
|
||||
MinBatteryLevelEntity(client, "bms_emsStatus.minDsgSoc", const.MIN_DISCHARGE_LEVEL, 0, 30,
|
||||
lambda value: {"moduleType": 2, "operateType": "dsgCfg",
|
||||
"params": {"minDsgSoc": int(value)}}),
|
||||
|
||||
ChargingPowerEntity(client, "mppt.cfgChgWatts", const.AC_CHARGING_POWER, 100, 360,
|
||||
lambda value: {"moduleType": 5, "operateType": "acChgCfg",
|
||||
"params": {"chgWatts": int(value), "chgPauseFlag": 255}}),
|
||||
]
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]:
|
||||
return [
|
||||
EnabledEntity(client, "mppt.cfgAcEnabled", const.AC_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "acOutCfg",
|
||||
"params": {"enabled": value, "out_voltage": -1, "out_freq": 255,
|
||||
"xboost": 255}}),
|
||||
|
||||
EnabledEntity(client, "mppt.cfgAcXboost", const.XBOOST_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "acOutCfg",
|
||||
"params": {"enabled": 255, "out_voltage": -1, "out_freq": 255,
|
||||
"xboost": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.carState", const.DC_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "mpptCar", "params": {"enabled": value}})
|
||||
]
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]:
|
||||
return [
|
||||
DictSelectEntity(client, "mppt.dcChgCurrent", const.DC_CHARGE_CURRENT, const.DC_CHARGE_CURRENT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "dcChgCfg",
|
||||
"params": {"dcChgCfg": value}}),
|
||||
|
||||
DictSelectEntity(client, "mppt.cfgChgType", const.DC_MODE, const.DC_MODE_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "chaType",
|
||||
"params": {"chaType": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "mppt.scrStandbyMin", const.SCREEN_TIMEOUT, const.SCREEN_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "lcdCfg",
|
||||
"params": {"brighLevel": 255, "delayOff": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "mppt.powStandbyMin", const.UNIT_TIMEOUT, const.UNIT_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "standby",
|
||||
"params": {"standbyMins": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "mppt.acStandbyMins", const.AC_TIMEOUT, const.AC_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "acStandby",
|
||||
"params": {"standbyMins": value}})
|
||||
]
|
||||
|
||||
def migrate(self, version) -> list[EntityMigration]:
|
||||
if version == 2:
|
||||
return [
|
||||
EntityMigration("pd.soc", Platform.SENSOR, MigrationAction.REMOVE),
|
||||
]
|
||||
return []
|
||||
163
config/custom_components/ecoflow_cloud/devices/river2_max.py
Normal file
163
config/custom_components/ecoflow_cloud/devices/river2_max.py
Normal file
@@ -0,0 +1,163 @@
|
||||
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, \
|
||||
MAIN_DESIGN_CAPACITY, MAIN_FULL_CAPACITY, MAIN_REMAIN_CAPACITY
|
||||
from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSwitchEntity, BaseSelectEntity
|
||||
from ..mqtt.ecoflow_mqtt import EcoflowMQTTClient
|
||||
from ..number import ChargingPowerEntity, MaxBatteryLevelEntity, MinBatteryLevelEntity, BatteryBackupLevel
|
||||
from ..select import DictSelectEntity, TimeoutDictSelectEntity
|
||||
from ..sensor import LevelSensorEntity, RemainSensorEntity, TempSensorEntity, \
|
||||
CyclesSensorEntity, InWattsSensorEntity, OutWattsSensorEntity, VoltSensorEntity, InAmpSensorEntity, \
|
||||
InVoltSensorEntity, QuotasStatusSensorEntity, MilliVoltSensorEntity, InMilliVoltSensorEntity, \
|
||||
OutMilliVoltSensorEntity, ChargingStateSensorEntity, CapacitySensorEntity
|
||||
from ..switch import EnabledEntity
|
||||
|
||||
|
||||
class River2Max(BaseDevice):
|
||||
|
||||
def charging_power_step(self) -> int:
|
||||
return 50
|
||||
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]:
|
||||
return [
|
||||
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),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.designCap", MAIN_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.fullCap", MAIN_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.remainCap", MAIN_REMAIN_CAPACITY, False),
|
||||
|
||||
LevelSensorEntity(client, "bms_bmsStatus.soh", const.SOH),
|
||||
|
||||
LevelSensorEntity(client, "bms_emsStatus.lcdShowSoc", const.COMBINED_BATTERY_LEVEL),
|
||||
|
||||
ChargingStateSensorEntity(client, "bms_emsStatus.chgState", BATTERY_CHARGING_STATE),
|
||||
|
||||
InWattsSensorEntity(client, "pd.wattsInSum", const.TOTAL_IN_POWER),
|
||||
OutWattsSensorEntity(client, "pd.wattsOutSum", const.TOTAL_OUT_POWER),
|
||||
|
||||
InAmpSensorEntity(client, "inv.dcInAmp", const.SOLAR_IN_CURRENT),
|
||||
InVoltSensorEntity(client, "inv.dcInVol", const.SOLAR_IN_VOLTAGE),
|
||||
|
||||
InWattsSensorEntity(client, "inv.inputWatts", const.AC_IN_POWER),
|
||||
OutWattsSensorEntity(client, "inv.outputWatts", const.AC_OUT_POWER),
|
||||
|
||||
InMilliVoltSensorEntity(client, "inv.acInVol", const.AC_IN_VOLT),
|
||||
OutMilliVoltSensorEntity(client, "inv.invOutVol", const.AC_OUT_VOLT),
|
||||
|
||||
InWattsSensorEntity(client, "pd.typecChaWatts", const.TYPE_C_IN_POWER),
|
||||
InWattsSensorEntity(client, "mppt.inWatts", const.SOLAR_IN_POWER),
|
||||
|
||||
|
||||
OutWattsSensorEntity(client, "pd.carWatts", const.DC_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.typec1Watts", const.TYPEC_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.usb1Watts", const.USB_OUT_POWER),
|
||||
# OutWattsSensorEntity(client, "pd.usb2Watts", const.USB_2_OUT_POWER),
|
||||
|
||||
RemainSensorEntity(client, "bms_emsStatus.chgRemainTime", const.CHARGE_REMAINING_TIME),
|
||||
RemainSensorEntity(client, "bms_emsStatus.dsgRemainTime", const.DISCHARGE_REMAINING_TIME),
|
||||
RemainSensorEntity(client, "pd.remainTime", const.REMAINING_TIME),
|
||||
|
||||
TempSensorEntity(client, "inv.outTemp", "Inv Out Temperature"),
|
||||
CyclesSensorEntity(client, "bms_bmsStatus.cycles", const.CYCLES),
|
||||
|
||||
TempSensorEntity(client, "bms_bmsStatus.temp", const.BATTERY_TEMP)
|
||||
.attr("bms_bmsStatus.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bms_bmsStatus.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bms_bmsStatus.minCellTemp", const.MIN_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "bms_bmsStatus.maxCellTemp", const.MAX_CELL_TEMP, False),
|
||||
|
||||
VoltSensorEntity(client, "bms_bmsStatus.vol", const.BATTERY_VOLT, False)
|
||||
.attr("bms_bmsStatus.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bms_bmsStatus.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bms_bmsStatus.minCellVol", const.MIN_CELL_VOLT, False),
|
||||
MilliVoltSensorEntity(client, "bms_bmsStatus.maxCellVol", const.MAX_CELL_VOLT, False),
|
||||
|
||||
QuotasStatusSensorEntity(client),
|
||||
# FanSensorEntity(client, "bms_emsStatus.fanLevel", "Fan Level"),
|
||||
|
||||
]
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]:
|
||||
return [
|
||||
MaxBatteryLevelEntity(client, "bms_emsStatus.maxChargeSoc", const.MAX_CHARGE_LEVEL, 50, 100,
|
||||
lambda value: {"moduleType": 2, "operateType": "upsConfig",
|
||||
"params": {"maxChgSoc": int(value)}}),
|
||||
|
||||
MinBatteryLevelEntity(client, "bms_emsStatus.minDsgSoc", const.MIN_DISCHARGE_LEVEL, 0, 30,
|
||||
lambda value: {"moduleType": 2, "operateType": "dsgCfg",
|
||||
"params": {"minDsgSoc": int(value)}}),
|
||||
|
||||
ChargingPowerEntity(client, "mppt.cfgChgWatts", const.AC_CHARGING_POWER, 50, 660,
|
||||
lambda value: {"moduleType": 5, "operateType": "acChgCfg",
|
||||
"params": {"chgWatts": int(value), "chgPauseFlag": 255}}),
|
||||
|
||||
BatteryBackupLevel(client, "pd.bpPowerSoc", const.BACKUP_RESERVE_LEVEL, 5, 100,
|
||||
"bms_emsStatus.minDsgSoc", "bms_emsStatus.maxChargeSoc",
|
||||
lambda value: {"moduleType": 1, "operateType": "watthConfig",
|
||||
"params": {"isConfig": 1,
|
||||
"bpPowerSoc": int(value),
|
||||
"minDsgSoc": 0,
|
||||
"minChgSoc": 0}}),
|
||||
]
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]:
|
||||
return [
|
||||
EnabledEntity(client, "mppt.cfgAcEnabled", const.AC_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "acOutCfg",
|
||||
"params": {"enabled": value, "out_voltage": -1, "out_freq": 255,
|
||||
"xboost": 255}}),
|
||||
|
||||
EnabledEntity(client, "pd.acAutoOutConfig", const.AC_ALWAYS_ENABLED,
|
||||
lambda value, params: {"moduleType": 1, "operateType": "acAutoOutConfig",
|
||||
"params": {"acAutoOutConfig": value,
|
||||
"minAcOutSoc": int(params.get("bms_emsStatus.minDsgSoc", 0)) + 5}}
|
||||
),
|
||||
|
||||
EnabledEntity(client, "mppt.cfgAcXboost", const.XBOOST_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "acOutCfg",
|
||||
"params": {"enabled": 255, "out_voltage": -1, "out_freq": 255,
|
||||
"xboost": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.carState", const.DC_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "mpptCar", "params": {"enabled": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.bpPowerSoc", const.BP_ENABLED,
|
||||
lambda value, params: {"moduleType": 1, "operateType": "watthConfig",
|
||||
"params": {"isConfig": value,
|
||||
"bpPowerSoc": value,
|
||||
"minDsgSoc": 0,
|
||||
"minChgSoc": 0}})
|
||||
]
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]:
|
||||
return [
|
||||
DictSelectEntity(client, "mppt.dcChgCurrent", const.DC_CHARGE_CURRENT, const.DC_CHARGE_CURRENT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "dcChgCfg",
|
||||
"params": {"dcChgCfg": value}}),
|
||||
|
||||
DictSelectEntity(client, "mppt.cfgChgType", const.DC_MODE, const.DC_MODE_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "chaType",
|
||||
"params": {"chaType": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "mppt.scrStandbyMin", const.SCREEN_TIMEOUT, const.SCREEN_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "lcdCfg",
|
||||
"params": {"brighLevel": 255, "delayOff": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "mppt.powStandbyMin", const.UNIT_TIMEOUT, const.UNIT_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "standby",
|
||||
"params": {"standbyMins": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "mppt.acStandbyMins", const.AC_TIMEOUT, const.AC_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "acStandby",
|
||||
"params": {"standbyMins": value}})
|
||||
]
|
||||
|
||||
def migrate(self, version) -> list[EntityMigration]:
|
||||
if version == 2:
|
||||
return [
|
||||
EntityMigration("pd.soc", Platform.SENSOR, MigrationAction.REMOVE),
|
||||
]
|
||||
return []
|
||||
136
config/custom_components/ecoflow_cloud/devices/river2_pro.py
Normal file
136
config/custom_components/ecoflow_cloud/devices/river2_pro.py
Normal file
@@ -0,0 +1,136 @@
|
||||
from homeassistant.const import Platform
|
||||
|
||||
from . import const, BaseDevice, EntityMigration, MigrationAction
|
||||
from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSwitchEntity, BaseSelectEntity
|
||||
from ..mqtt.ecoflow_mqtt import EcoflowMQTTClient
|
||||
from ..number import ChargingPowerEntity, MaxBatteryLevelEntity, MinBatteryLevelEntity
|
||||
from ..select import DictSelectEntity, TimeoutDictSelectEntity
|
||||
from ..sensor import LevelSensorEntity, RemainSensorEntity, TempSensorEntity, \
|
||||
CyclesSensorEntity, InWattsSensorEntity, OutWattsSensorEntity, VoltSensorEntity, QuotasStatusSensorEntity, \
|
||||
MilliVoltSensorEntity, InMilliVoltSensorEntity, OutMilliVoltSensorEntity, ChargingStateSensorEntity, \
|
||||
CapacitySensorEntity
|
||||
from ..switch import EnabledEntity
|
||||
|
||||
|
||||
class River2Pro(BaseDevice):
|
||||
|
||||
def charging_power_step(self) -> int:
|
||||
return 50
|
||||
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]:
|
||||
return [
|
||||
LevelSensorEntity(client, "bms_bmsStatus.soc", const.MAIN_BATTERY_LEVEL)
|
||||
.attr("bms_bmsStatus.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bms_bmsStatus.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bms_bmsStatus.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.designCap", const.MAIN_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.fullCap", const.MAIN_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bms_bmsStatus.remainCap", const.MAIN_REMAIN_CAPACITY, False),
|
||||
|
||||
LevelSensorEntity(client, "bms_bmsStatus.soh", const.SOH),
|
||||
LevelSensorEntity(client, "bms_emsStatus.lcdShowSoc", const.COMBINED_BATTERY_LEVEL),
|
||||
|
||||
ChargingStateSensorEntity(client, "bms_emsStatus.chgState", const.BATTERY_CHARGING_STATE),
|
||||
|
||||
InWattsSensorEntity(client, "pd.wattsInSum", const.TOTAL_IN_POWER),
|
||||
OutWattsSensorEntity(client, "pd.wattsOutSum", const.TOTAL_OUT_POWER),
|
||||
|
||||
InWattsSensorEntity(client, "inv.inputWatts", const.AC_IN_POWER),
|
||||
OutWattsSensorEntity(client, "inv.outputWatts", const.AC_OUT_POWER),
|
||||
|
||||
InMilliVoltSensorEntity(client, "inv.acInVol", const.AC_IN_VOLT),
|
||||
OutMilliVoltSensorEntity(client, "inv.invOutVol", const.AC_OUT_VOLT),
|
||||
|
||||
InWattsSensorEntity(client, "pd.typecChaWatts", const.TYPE_C_IN_POWER),
|
||||
InWattsSensorEntity(client, "mppt.inWatts", const.SOLAR_IN_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.carWatts", const.DC_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.typec1Watts", const.TYPEC_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.usb1Watts", const.USB_OUT_POWER),
|
||||
# OutWattsSensorEntity(client, "pd.usb2Watts", const.USB_2_OUT_POWER),
|
||||
|
||||
RemainSensorEntity(client, "bms_emsStatus.chgRemainTime", const.CHARGE_REMAINING_TIME),
|
||||
RemainSensorEntity(client, "bms_emsStatus.dsgRemainTime", const.DISCHARGE_REMAINING_TIME),
|
||||
RemainSensorEntity(client, "pd.remainTime", const.REMAINING_TIME),
|
||||
|
||||
|
||||
TempSensorEntity(client, "inv.outTemp", "Inv Out Temperature"),
|
||||
CyclesSensorEntity(client, "bms_bmsStatus.cycles", const.CYCLES),
|
||||
|
||||
TempSensorEntity(client, "bms_bmsStatus.temp", const.BATTERY_TEMP)
|
||||
.attr("bms_bmsStatus.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bms_bmsStatus.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bms_bmsStatus.minCellTemp", const.MIN_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "bms_bmsStatus.maxCellTemp", const.MAX_CELL_TEMP, False),
|
||||
|
||||
VoltSensorEntity(client, "bms_bmsStatus.vol", const.BATTERY_VOLT, False)
|
||||
.attr("bms_bmsStatus.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bms_bmsStatus.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bms_bmsStatus.minCellVol", const.MIN_CELL_VOLT, False),
|
||||
MilliVoltSensorEntity(client, "bms_bmsStatus.maxCellVol", const.MAX_CELL_VOLT, False),
|
||||
# FanSensorEntity(client, "bms_emsStatus.fanLevel", "Fan Level"),
|
||||
|
||||
QuotasStatusSensorEntity(client),
|
||||
|
||||
]
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]:
|
||||
return [
|
||||
MaxBatteryLevelEntity(client, "bms_emsStatus.maxChargeSoc", const.MAX_CHARGE_LEVEL, 50, 100,
|
||||
lambda value: {"moduleType": 2, "operateType": "upsConfig",
|
||||
"params": {"maxChgSoc": int(value)}}),
|
||||
|
||||
MinBatteryLevelEntity(client, "bms_emsStatus.minDsgSoc", const.MIN_DISCHARGE_LEVEL, 0, 30,
|
||||
lambda value: {"moduleType": 2, "operateType": "dsgCfg",
|
||||
"params": {"minDsgSoc": int(value)}}),
|
||||
|
||||
ChargingPowerEntity(client, "mppt.cfgChgWatts", const.AC_CHARGING_POWER, 100, 950,
|
||||
lambda value: {"moduleType": 5, "operateType": "acChgCfg",
|
||||
"params": {"chgWatts": int(value), "chgPauseFlag": 255}}),
|
||||
]
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]:
|
||||
return [
|
||||
EnabledEntity(client, "mppt.cfgAcEnabled", const.AC_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "acOutCfg",
|
||||
"params": {"enabled": value, "out_voltage": -1, "out_freq": 255,
|
||||
"xboost": 255}}),
|
||||
|
||||
EnabledEntity(client, "mppt.cfgAcXboost", const.XBOOST_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "acOutCfg",
|
||||
"params": {"enabled": 255, "out_voltage": -1, "out_freq": 255,
|
||||
"xboost": value}}),
|
||||
|
||||
EnabledEntity(client, "pd.carState", const.DC_ENABLED,
|
||||
lambda value: {"moduleType": 5, "operateType": "mpptCar", "params": {"enabled": value}})
|
||||
]
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]:
|
||||
return [
|
||||
DictSelectEntity(client, "mppt.dcChgCurrent", const.DC_CHARGE_CURRENT, const.DC_CHARGE_CURRENT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "dcChgCfg",
|
||||
"params": {"dcChgCfg": value}}),
|
||||
|
||||
DictSelectEntity(client, "mppt.cfgChgType", const.DC_MODE, const.DC_MODE_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "chaType",
|
||||
"params": {"chaType": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "mppt.scrStandbyMin", const.SCREEN_TIMEOUT, const.SCREEN_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "lcdCfg",
|
||||
"params": {"brighLevel": 255, "delayOff": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "mppt.powStandbyMin", const.UNIT_TIMEOUT, const.UNIT_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "standby",
|
||||
"params": {"standbyMins": value}}),
|
||||
|
||||
TimeoutDictSelectEntity(client, "mppt.acStandbyMins", const.AC_TIMEOUT, const.AC_TIMEOUT_OPTIONS,
|
||||
lambda value: {"moduleType": 5, "operateType": "acStandby",
|
||||
"params": {"standbyMins": value}})
|
||||
]
|
||||
|
||||
def migrate(self, version) -> list[EntityMigration]:
|
||||
if version == 2:
|
||||
return [
|
||||
EntityMigration("pd.soc", Platform.SENSOR, MigrationAction.REMOVE),
|
||||
]
|
||||
return []
|
||||
117
config/custom_components/ecoflow_cloud/devices/river_max.py
Normal file
117
config/custom_components/ecoflow_cloud/devices/river_max.py
Normal file
@@ -0,0 +1,117 @@
|
||||
from homeassistant.const import Platform
|
||||
|
||||
from . import const, BaseDevice, MigrationAction, EntityMigration
|
||||
from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSwitchEntity, BaseSelectEntity
|
||||
from ..mqtt.ecoflow_mqtt import EcoflowMQTTClient
|
||||
from ..number import MaxBatteryLevelEntity
|
||||
from ..select import DictSelectEntity
|
||||
from ..sensor import LevelSensorEntity, WattsSensorEntity, RemainSensorEntity, TempSensorEntity, \
|
||||
CyclesSensorEntity, InWattsSensorEntity, OutWattsSensorEntity, StatusSensorEntity, \
|
||||
InEnergySensorEntity, OutEnergySensorEntity, MilliVoltSensorEntity, InMilliVoltSensorEntity, \
|
||||
OutMilliVoltSensorEntity, CapacitySensorEntity
|
||||
from ..switch import EnabledEntity, BeeperEntity
|
||||
|
||||
|
||||
class RiverMax(BaseDevice):
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]:
|
||||
return [
|
||||
LevelSensorEntity(client, "bmsMaster.soc", const.MAIN_BATTERY_LEVEL)
|
||||
.attr("bmsMaster.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bmsMaster.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bmsMaster.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bmsMaster.designCap", const.MAIN_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsMaster.fullCap", const.MAIN_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsMaster.remainCap", const.MAIN_REMAIN_CAPACITY, False),
|
||||
|
||||
WattsSensorEntity(client, "pd.wattsInSum", const.TOTAL_IN_POWER),
|
||||
WattsSensorEntity(client, "pd.wattsOutSum", const.TOTAL_OUT_POWER),
|
||||
|
||||
InWattsSensorEntity(client, "inv.inputWatts", const.AC_IN_POWER),
|
||||
OutWattsSensorEntity(client, "inv.outputWatts", const.AC_OUT_POWER),
|
||||
|
||||
InMilliVoltSensorEntity(client, "inv.acInVol", const.AC_IN_VOLT),
|
||||
OutMilliVoltSensorEntity(client, "inv.invOutVol", const.AC_OUT_VOLT),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.carWatts", const.DC_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.typecWatts", const.TYPEC_OUT_POWER),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.usb1Watts", const.USB_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.usb2Watts", const.USB_2_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.usb3Watts", const.USB_3_OUT_POWER),
|
||||
|
||||
RemainSensorEntity(client, "pd.remainTime", const.REMAINING_TIME),
|
||||
CyclesSensorEntity(client, "bmsMaster.cycles", const.CYCLES),
|
||||
|
||||
TempSensorEntity(client, "bmsMaster.temp", const.BATTERY_TEMP)
|
||||
.attr("bmsMaster.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bmsMaster.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bmsMaster.minCellTemp", const.MIN_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "bmsMaster.maxCellTemp", const.MAX_CELL_TEMP, False),
|
||||
|
||||
MilliVoltSensorEntity(client, "bmsMaster.vol", const.BATTERY_VOLT, False)
|
||||
.attr("bmsMaster.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bmsMaster.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bmsMaster.minCellVol", const.MIN_CELL_VOLT, False),
|
||||
MilliVoltSensorEntity(client, "bmsMaster.maxCellVol", const.MAX_CELL_VOLT, False),
|
||||
|
||||
# https://github.com/tolwi/hassio-ecoflow-cloud/discussions/87
|
||||
InEnergySensorEntity(client, "pd.chgSunPower", const.SOLAR_IN_ENERGY),
|
||||
InEnergySensorEntity(client, "pd.chgPowerAC", const.CHARGE_AC_ENERGY),
|
||||
InEnergySensorEntity(client, "pd.chgPowerDC", const.CHARGE_DC_ENERGY),
|
||||
OutEnergySensorEntity(client, "pd.dsgPowerAC", const.DISCHARGE_AC_ENERGY),
|
||||
OutEnergySensorEntity(client, "pd.dsgPowerDC", const.DISCHARGE_DC_ENERGY),
|
||||
|
||||
LevelSensorEntity(client, "bmsSlave1.soc", const.SLAVE_BATTERY_LEVEL, False, True)
|
||||
.attr("bmsSlave1.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bmsSlave1.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bmsSlave1.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bmsSlave1.designCap", const.SLAVE_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsSlave1.fullCap", const.SLAVE_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsSlave1.remainCap", const.SLAVE_REMAIN_CAPACITY, False),
|
||||
|
||||
TempSensorEntity(client, "bmsSlave1.temp", const.SLAVE_BATTERY_TEMP, False, True)
|
||||
.attr("bmsSlave1.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bmsSlave1.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bmsSlave1.minCellTemp", const.SLAVE_MIN_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "bmsSlave1.maxCellTemp", const.SLAVE_MAX_CELL_TEMP, False),
|
||||
|
||||
MilliVoltSensorEntity(client, "bmsSlave1.vol", const.BATTERY_VOLT, False)
|
||||
.attr("bmsSlave1.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bmsSlave1.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bmsSlave1.minCellVol", const.MIN_CELL_VOLT, False),
|
||||
MilliVoltSensorEntity(client, "bmsSlave1.maxCellVol", const.MAX_CELL_VOLT, False),
|
||||
|
||||
CyclesSensorEntity(client, "bmsSlave1.cycles", const.SLAVE_CYCLES, False, True),
|
||||
StatusSensorEntity(client),
|
||||
]
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]:
|
||||
return [
|
||||
MaxBatteryLevelEntity(client, "bmsMaster.maxChargeSoc", const.MAX_CHARGE_LEVEL, 30, 100, None),
|
||||
# MinBatteryLevelEntity(client, "bmsMaster.minDsgSoc", const.MIN_DISCHARGE_LEVEL, 0, 30, None),
|
||||
]
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]:
|
||||
return [
|
||||
BeeperEntity(client, "pd.beepState", const.BEEPER, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 38, "enabled": value}}),
|
||||
EnabledEntity(client, "inv.cfgAcEnabled", const.AC_ENABLED, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 66, "enabled": value}}),
|
||||
EnabledEntity(client, "pd.carSwitch", const.DC_ENABLED, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 34, "enabled": value}}),
|
||||
EnabledEntity(client, "inv.cfgAcXboost", const.XBOOST_ENABLED, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 66, "xboost": value}})
|
||||
|
||||
]
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]:
|
||||
return [
|
||||
|
||||
DictSelectEntity(client, "pd.standByMode", const.UNIT_TIMEOUT, const.UNIT_TIMEOUT_OPTIONS, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 33, "standByMode": value}}),
|
||||
DictSelectEntity(client, "inv.cfgStandbyMin", const.AC_TIMEOUT, const.AC_TIMEOUT_OPTIONS, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 153, "standByMins": value}}),
|
||||
|
||||
]
|
||||
|
||||
def migrate(self, version) -> list[EntityMigration]:
|
||||
if version == 2:
|
||||
return [
|
||||
EntityMigration("pd.soc", Platform.SENSOR, MigrationAction.REMOVE),
|
||||
]
|
||||
return []
|
||||
138
config/custom_components/ecoflow_cloud/devices/river_pro.py
Normal file
138
config/custom_components/ecoflow_cloud/devices/river_pro.py
Normal file
@@ -0,0 +1,138 @@
|
||||
from homeassistant.const import Platform
|
||||
|
||||
from . import const, BaseDevice, EntityMigration, MigrationAction
|
||||
from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSwitchEntity, BaseSelectEntity
|
||||
from ..mqtt.ecoflow_mqtt import EcoflowMQTTClient
|
||||
from ..number import MaxBatteryLevelEntity
|
||||
from ..select import TimeoutDictSelectEntity
|
||||
from ..sensor import LevelSensorEntity, WattsSensorEntity, RemainSensorEntity, TempSensorEntity, \
|
||||
CyclesSensorEntity, InEnergySensorEntity, InWattsSensorEntity, OutEnergySensorEntity, OutWattsSensorEntity, VoltSensorEntity, InVoltSensorEntity, \
|
||||
InAmpSensorEntity, AmpSensorEntity, StatusSensorEntity, MilliVoltSensorEntity, InMilliVoltSensorEntity, \
|
||||
OutMilliVoltSensorEntity, CapacitySensorEntity
|
||||
from ..switch import EnabledEntity, BeeperEntity
|
||||
|
||||
|
||||
class RiverPro(BaseDevice):
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]:
|
||||
return [
|
||||
LevelSensorEntity(client, "bmsMaster.soc", const.MAIN_BATTERY_LEVEL)
|
||||
.attr("bmsMaster.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bmsMaster.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bmsMaster.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bmsMaster.designCap", const.MAIN_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsMaster.fullCap", const.MAIN_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsMaster.remainCap", const.MAIN_REMAIN_CAPACITY, False),
|
||||
|
||||
WattsSensorEntity(client, "pd.wattsInSum", const.TOTAL_IN_POWER),
|
||||
WattsSensorEntity(client, "pd.wattsOutSum", const.TOTAL_OUT_POWER),
|
||||
|
||||
InAmpSensorEntity(client, "inv.dcInAmp", const.SOLAR_IN_CURRENT),
|
||||
InVoltSensorEntity(client, "inv.dcInVol", const.SOLAR_IN_VOLTAGE),
|
||||
|
||||
InWattsSensorEntity(client, "inv.inputWatts", const.AC_IN_POWER),
|
||||
OutWattsSensorEntity(client, "inv.outputWatts", const.AC_OUT_POWER),
|
||||
|
||||
InMilliVoltSensorEntity(client, "inv.acInVol", const.AC_IN_VOLT),
|
||||
OutMilliVoltSensorEntity(client, "inv.invOutVol", const.AC_OUT_VOLT),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.carWatts", const.DC_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.typecWatts", const.TYPEC_OUT_POWER),
|
||||
# disabled by default because they aren't terribly useful
|
||||
TempSensorEntity(client, "pd.carTemp", const.DC_CAR_OUT_TEMP, False),
|
||||
TempSensorEntity(client, "pd.typecTemp", const.USB_C_TEMP, False),
|
||||
|
||||
OutWattsSensorEntity(client, "pd.usb1Watts", const.USB_1_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.usb2Watts", const.USB_2_OUT_POWER),
|
||||
OutWattsSensorEntity(client, "pd.usb3Watts", const.USB_3_OUT_POWER),
|
||||
|
||||
RemainSensorEntity(client, "pd.remainTime", const.REMAINING_TIME),
|
||||
TempSensorEntity(client, "bmsMaster.temp", const.BATTERY_TEMP)
|
||||
.attr("bmsMaster.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bmsMaster.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
|
||||
TempSensorEntity(client, "bmsMaster.minCellTemp", const.MIN_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "bmsMaster.maxCellTemp", const.MAX_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "inv.inTemp", const.INV_IN_TEMP),
|
||||
TempSensorEntity(client, "inv.outTemp", const.INV_OUT_TEMP),
|
||||
|
||||
# https://github.com/tolwi/hassio-ecoflow-cloud/discussions/87
|
||||
InEnergySensorEntity(client, "pd.chgSunPower", const.SOLAR_IN_ENERGY),
|
||||
InEnergySensorEntity(client, "pd.chgPowerAC", const.CHARGE_AC_ENERGY),
|
||||
InEnergySensorEntity(client, "pd.chgPowerDC", const.CHARGE_DC_ENERGY),
|
||||
OutEnergySensorEntity(client, "pd.dsgPowerAC", const.DISCHARGE_AC_ENERGY),
|
||||
OutEnergySensorEntity(client, "pd.dsgPowerDC", const.DISCHARGE_DC_ENERGY),
|
||||
|
||||
AmpSensorEntity(client, "bmsMaster.amp", const.BATTERY_AMP, False),
|
||||
MilliVoltSensorEntity(client, "bmsMaster.vol", const.BATTERY_VOLT, False)
|
||||
.attr("bmsMaster.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bmsMaster.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bmsMaster.minCellVol", const.MIN_CELL_VOLT, False),
|
||||
MilliVoltSensorEntity(client, "bmsMaster.maxCellVol", const.MAX_CELL_VOLT, False),
|
||||
|
||||
CyclesSensorEntity(client, "bmsMaster.cycles", const.CYCLES),
|
||||
|
||||
|
||||
# Optional Slave Batteries
|
||||
LevelSensorEntity(client, "bmsSlave1.soc", const.SLAVE_BATTERY_LEVEL, False, True)
|
||||
.attr("bmsSlave1.designCap", const.ATTR_DESIGN_CAPACITY, 0)
|
||||
.attr("bmsSlave1.fullCap", const.ATTR_FULL_CAPACITY, 0)
|
||||
.attr("bmsSlave1.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bmsSlave1.designCap", const.SLAVE_DESIGN_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsSlave1.fullCap", const.SLAVE_FULL_CAPACITY, False),
|
||||
CapacitySensorEntity(client, "bmsSlave1.remainCap", const.SLAVE_REMAIN_CAPACITY, False),
|
||||
|
||||
CyclesSensorEntity(client, "bmsSlave1.cycles", const.SLAVE_CYCLES, False, True),
|
||||
TempSensorEntity(client, "bmsSlave1.temp", const.SLAVE_BATTERY_TEMP, False, True)
|
||||
.attr("bmsSlave1.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bmsSlave1.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
|
||||
AmpSensorEntity(client, "bmsSlave1.amp", const.SLAVE_BATTERY_AMP, False),
|
||||
MilliVoltSensorEntity(client, "bmsSlave1.vol", const.SLAVE_BATTERY_VOLT, False)
|
||||
.attr("bmsSlave1.minCellVol", const.ATTR_MIN_CELL_VOLT, 0)
|
||||
.attr("bmsSlave1.maxCellVol", const.ATTR_MAX_CELL_VOLT, 0),
|
||||
MilliVoltSensorEntity(client, "bmsSlave1.minCellVol", const.SLAVE_MIN_CELL_VOLT, False),
|
||||
MilliVoltSensorEntity(client, "bmsSlave1.maxCellVol", const.SLAVE_MAX_CELL_VOLT, False),
|
||||
|
||||
|
||||
StatusSensorEntity(client),
|
||||
|
||||
]
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]:
|
||||
return [
|
||||
MaxBatteryLevelEntity(client, "bmsMaster.maxChargeSoc", const.MAX_CHARGE_LEVEL, 30, 100,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
"params": {"id": 49, "maxChgSoc": value}}),
|
||||
# MinBatteryLevelEntity(client, "bmsMaster.minDsgSoc", const.MIN_DISCHARGE_LEVEL, 0, 30, None),
|
||||
]
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[BaseSwitchEntity]:
|
||||
return [
|
||||
BeeperEntity(client, "pd.beepState", const.BEEPER, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 38, "enabled": value}}),
|
||||
EnabledEntity(client, "inv.acAutoOutConfig", const.AC_ALWAYS_ENABLED,
|
||||
lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 95, "acautooutConfig": value, "minAcoutSoc": 255}}),
|
||||
EnabledEntity(client, "pd.carSwitch", const.DC_ENABLED, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 34, "enabled": value}}),
|
||||
EnabledEntity(client, "inv.cfgAcEnabled", const.AC_ENABLED, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 66, "enabled": value}}),
|
||||
EnabledEntity(client, "inv.cfgAcXboost", const.XBOOST_ENABLED, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 66, "xboost": value}}),
|
||||
EnabledEntity(client, "inv.cfgAcChgModeFlg", const.AC_SLOW_CHARGE, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 65, "workMode": value}}),
|
||||
EnabledEntity(client, "inv.cfgFanMode", const.AUTO_FAN_SPEED, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 73, "fanMode": value}})
|
||||
]
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]:
|
||||
return [
|
||||
TimeoutDictSelectEntity(client, "pd.standByMode", const.UNIT_TIMEOUT, const.UNIT_TIMEOUT_OPTIONS_LIMITED, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 33, "standByMode": value}}),
|
||||
TimeoutDictSelectEntity(client, "pd.carDelayOffMin", const.DC_TIMEOUT, const.DC_TIMEOUT_OPTIONS_LIMITED, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"cmdSet": 32, "id": 84, "carDelayOffMin": value}}),
|
||||
TimeoutDictSelectEntity(client, "inv.cfgStandbyMin", const.AC_TIMEOUT, const.AC_TIMEOUT_OPTIONS_LIMITED, lambda value: {"moduleType": 0, "operateType": "TCP", "params": {"id": 153, "standByMins": value}})
|
||||
|
||||
# lambda is confirmed correct, but pd.lcdOffSec is missing from status
|
||||
# TimeoutDictSelectEntity(client, "pd.lcdOffSec", const.SCREEN_TIMEOUT, const.SCREEN_TIMEOUT_OPTIONS,
|
||||
# lambda value: {"moduleType": 0, "operateType": "TCP",
|
||||
# "params": {"lcdTime": value, "id": 39}})
|
||||
]
|
||||
|
||||
def migrate(self, version) -> list[EntityMigration]:
|
||||
if version == 2:
|
||||
return [
|
||||
EntityMigration("pd.soc", Platform.SENSOR, MigrationAction.REMOVE),
|
||||
]
|
||||
return []
|
||||
91
config/custom_components/ecoflow_cloud/devices/wave2.py
Normal file
91
config/custom_components/ecoflow_cloud/devices/wave2.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
|
||||
from . import const, BaseDevice
|
||||
from .. import EcoflowMQTTClient
|
||||
from ..entities import BaseSensorEntity, BaseNumberEntity, BaseSelectEntity
|
||||
from ..number import SetTempEntity
|
||||
from ..select import DictSelectEntity
|
||||
from ..sensor import LevelSensorEntity, RemainSensorEntity, TempSensorEntity, \
|
||||
WattsSensorEntity, QuotasStatusSensorEntity, \
|
||||
MilliCelsiusSensorEntity, CapacitySensorEntity
|
||||
|
||||
|
||||
class Wave2(BaseDevice):
|
||||
def sensors(self, client: EcoflowMQTTClient) -> list[BaseSensorEntity]:
|
||||
return [
|
||||
# Power and Battery Entities
|
||||
LevelSensorEntity(client, "bms.soc", const.MAIN_BATTERY_LEVEL)
|
||||
.attr("bms.remainCap", const.ATTR_REMAIN_CAPACITY, 0),
|
||||
CapacitySensorEntity(client, "bms.remainCap", const.MAIN_REMAIN_CAPACITY, False),
|
||||
|
||||
TempSensorEntity(client, "bms.tmp", const.BATTERY_TEMP)
|
||||
.attr("bms.minCellTemp", const.ATTR_MIN_CELL_TEMP, 0)
|
||||
.attr("bms.maxCellTemp", const.ATTR_MAX_CELL_TEMP, 0),
|
||||
TempSensorEntity(client, "bms.minCellTmp", const.MIN_CELL_TEMP, False),
|
||||
TempSensorEntity(client, "bms.maxCellTmp", const.MAX_CELL_TEMP, False),
|
||||
|
||||
RemainSensorEntity(client, "pd.batChgRemain", const.CHARGE_REMAINING_TIME),
|
||||
RemainSensorEntity(client, "pd.batDsgRemain", const.DISCHARGE_REMAINING_TIME),
|
||||
|
||||
# heat pump
|
||||
MilliCelsiusSensorEntity(client, "pd.condTemp", "Condensation temperature", False),
|
||||
MilliCelsiusSensorEntity(client, "pd.heatEnv", "Return air temperature in condensation zone", False),
|
||||
MilliCelsiusSensorEntity(client, "pd.coolEnv", "Air outlet temperature", False),
|
||||
MilliCelsiusSensorEntity(client, "pd.evapTemp", "Evaporation temperature", False),
|
||||
MilliCelsiusSensorEntity(client, "pd.motorOutTemp", "Exhaust temperature", False),
|
||||
MilliCelsiusSensorEntity(client, "pd.airInTemp", "Evaporation zone return air temperature", False),
|
||||
|
||||
TempSensorEntity(client, "pd.coolTemp", "Air outlet temperature", False),
|
||||
TempSensorEntity(client, "pd.envTemp", "Ambient temperature", False),
|
||||
|
||||
# power (pd)
|
||||
WattsSensorEntity(client, "pd.mpptPwr", "PV input power"),
|
||||
WattsSensorEntity(client, "pd.batPwrOut", "Battery output power"),
|
||||
WattsSensorEntity(client, "pd.pvPower", "PV charging power"),
|
||||
WattsSensorEntity(client, "pd.acPwrIn", "AC input power"),
|
||||
WattsSensorEntity(client, "pd.psdrPower ", "Power supply power"),
|
||||
WattsSensorEntity(client, "pd.sysPowerWatts", "System power"),
|
||||
WattsSensorEntity(client, "pd.batPower ", "Battery power"),
|
||||
|
||||
# power (motor)
|
||||
WattsSensorEntity(client, "motor.power", "Motor operating power"),
|
||||
|
||||
# power (power)
|
||||
WattsSensorEntity(client, "power.batPwrOut", "Battery output power"),
|
||||
WattsSensorEntity(client, "power.acPwrI", "AC input power"),
|
||||
WattsSensorEntity(client, "power.mpptPwr ", "PV input power"),
|
||||
|
||||
QuotasStatusSensorEntity(client)
|
||||
|
||||
]
|
||||
|
||||
def numbers(self, client: EcoflowMQTTClient) -> list[BaseNumberEntity]:
|
||||
return [
|
||||
SetTempEntity(client, "pd.setTemp", "Set Temperature", 0, 40,
|
||||
lambda value: {"moduleType": 1, "operateType": "setTemp",
|
||||
"sn": client.device_sn,
|
||||
"params": {"setTemp": int(value)}}),
|
||||
]
|
||||
|
||||
def selects(self, client: EcoflowMQTTClient) -> list[BaseSelectEntity]:
|
||||
return [
|
||||
DictSelectEntity(client, "pd.fanValue", const.FAN_MODE, const.FAN_MODE_OPTIONS,
|
||||
lambda value: {"moduleType": 1, "operateType": "fanValue",
|
||||
"sn": client.device_sn,
|
||||
"params": {"fanValue": value}}),
|
||||
DictSelectEntity(client, "pd.mainMode", const.MAIN_MODE, const.MAIN_MODE_OPTIONS,
|
||||
lambda value: {"moduleType": 1, "operateType": "mainMode",
|
||||
"sn": client.device_sn,
|
||||
"params": {"mainMode": value}}),
|
||||
DictSelectEntity(client, "pd.powerMode", const.REMOTE_MODE, const.REMOTE_MODE_OPTIONS,
|
||||
lambda value: {"moduleType": 1, "operateType": "powerMode",
|
||||
"sn": client.device_sn,
|
||||
"params": {"powerMode": value}}),
|
||||
DictSelectEntity(client, "pd.subMode", const.POWER_SUB_MODE, const.POWER_SUB_MODE_OPTIONS,
|
||||
lambda value: {"moduleType": 1, "operateType": "subMode",
|
||||
"sn": client.device_sn,
|
||||
"params": {"subMode": value}}),
|
||||
]
|
||||
|
||||
def switches(self, client: EcoflowMQTTClient) -> list[SwitchEntity]:
|
||||
return []
|
||||
30
config/custom_components/ecoflow_cloud/diagnostics.py
Normal file
30
config/custom_components/ecoflow_cloud/diagnostics.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import DOMAIN
|
||||
from .mqtt.ecoflow_mqtt import EcoflowMQTTClient
|
||||
|
||||
|
||||
def _to_serializable(x):
|
||||
t = type(x)
|
||||
if t is dict:
|
||||
x = {y: _to_serializable(x[y]) for y in x}
|
||||
if t is timedelta:
|
||||
x = x.__str__()
|
||||
return x
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(hass: HomeAssistant, entry: ConfigEntry):
|
||||
client: EcoflowMQTTClient = hass.data[DOMAIN][entry.entry_id]
|
||||
values = {
|
||||
'device': client.device_type,
|
||||
'params': dict(sorted(client.data.params.items())),
|
||||
'set': [dict(sorted(k.items())) for k in client.data.set],
|
||||
'set_reply': [dict(sorted(k.items())) for k in client.data.set_reply],
|
||||
'get': [dict(sorted(k.items())) for k in client.data.get],
|
||||
'get_reply': [dict(sorted(k.items())) for k in client.data.get_reply],
|
||||
'raw_data': client.data.raw_data,
|
||||
}
|
||||
return values
|
||||
151
config/custom_components/ecoflow_cloud/entities/__init__.py
Normal file
151
config/custom_components/ecoflow_cloud/entities/__init__.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from __future__ import annotations
|
||||
import inspect
|
||||
from typing import Any, Callable, Optional, OrderedDict, Mapping
|
||||
|
||||
from homeassistant.components.number import NumberEntity
|
||||
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
|
||||
|
||||
|
||||
class EcoFlowAbstractEntity(Entity):
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(self, client: EcoflowMQTTClient, title: str, key: str):
|
||||
self._client = client
|
||||
self._attr_name = title
|
||||
self._attr_device_info = client.device_info_main
|
||||
self._attr_unique_id = self.gen_unique_id(client.device_sn, key)
|
||||
|
||||
def send_get_message(self, command: dict):
|
||||
self._client.send_get_message(command)
|
||||
|
||||
def send_set_message(self, target_dict: dict[str, Any] | None, command: dict):
|
||||
self._client.send_set_message(target_dict, command)
|
||||
|
||||
@staticmethod
|
||||
def gen_unique_id(sn: str, key: str):
|
||||
return 'ecoflow-' + sn + '-' + key.replace('.', '-').replace('_', '-')
|
||||
|
||||
|
||||
class EcoFlowDictEntity(EcoFlowAbstractEntity):
|
||||
|
||||
def __init__(self, client: EcoflowMQTTClient, mqtt_key: str, title: str, enabled: bool = True,
|
||||
auto_enable: bool = False) -> object:
|
||||
super().__init__(client, title, mqtt_key)
|
||||
self._mqtt_key = mqtt_key
|
||||
self._auto_enable = auto_enable
|
||||
self._attr_entity_registry_enabled_default = enabled
|
||||
self.__attributes_mapping: dict[str, str] = {}
|
||||
self.__attrs = OrderedDict[str, Any]()
|
||||
|
||||
def attr(self, mqtt_key: str, title: str, default: Any) -> EcoFlowDictEntity:
|
||||
self.__attributes_mapping[mqtt_key] = title
|
||||
self.__attrs[title] = default
|
||||
return self
|
||||
|
||||
@property
|
||||
def mqtt_key(self):
|
||||
return self._mqtt_key
|
||||
|
||||
@property
|
||||
def auto_enable(self):
|
||||
return self._auto_enable
|
||||
|
||||
def send_set_message(self, target_value: Any, command: dict):
|
||||
super().send_set_message({self._mqtt_key: target_value}, command)
|
||||
|
||||
@property
|
||||
def enabled_default(self):
|
||||
return self._attr_entity_registry_enabled_default
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
await super().async_added_to_hass()
|
||||
d = self._client.data.params_observable().subscribe(self._updated)
|
||||
self.async_on_remove(d.dispose)
|
||||
|
||||
def _updated(self, data: dict[str, Any]):
|
||||
# update attributes
|
||||
for key, title in self.__attributes_mapping.items():
|
||||
if key in data:
|
||||
self.__attrs[title] = data[key]
|
||||
|
||||
# update value
|
||||
if self._mqtt_key in data:
|
||||
self._attr_available = True
|
||||
if self._auto_enable:
|
||||
self._attr_entity_registry_enabled_default = True
|
||||
|
||||
if self._update_value(data[self._mqtt_key]):
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> Mapping[str, Any] | None:
|
||||
return self.__attrs
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class EcoFlowBaseCommandEntity(EcoFlowDictEntity):
|
||||
def __init__(self, client: EcoflowMQTTClient, mqtt_key: str, title: str,
|
||||
command: Callable[[int, Optional[dict[str, Any]]], dict[str, Any]] | None,
|
||||
enabled: bool = True, auto_enable: bool = False):
|
||||
super().__init__(client, mqtt_key, title, enabled, auto_enable)
|
||||
self._command = command
|
||||
|
||||
def command_dict(self, value: int) -> dict[str, any] | None:
|
||||
if self._command:
|
||||
p_count = len(inspect.signature(self._command).parameters)
|
||||
if p_count == 1:
|
||||
return self._command(value)
|
||||
elif p_count == 2:
|
||||
return self._command(value, self._client.data.params)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class BaseNumberEntity(NumberEntity, EcoFlowBaseCommandEntity):
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
|
||||
def __init__(self, client: EcoflowMQTTClient, mqtt_key: str, title: str, min_value: int, max_value: int,
|
||||
command: Callable[[int], dict[str, any]] | None, enabled: bool = True,
|
||||
auto_enable: bool = False):
|
||||
super().__init__(client, mqtt_key, title, command, enabled, auto_enable)
|
||||
self._attr_native_max_value = max_value
|
||||
self._attr_native_min_value = min_value
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
if self._attr_native_value != val:
|
||||
self._attr_native_value = val
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class BaseSensorEntity(SensorEntity, EcoFlowDictEntity):
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
if self._attr_native_value != val:
|
||||
self._attr_native_value = val
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class BaseSwitchEntity(SwitchEntity, EcoFlowBaseCommandEntity):
|
||||
pass
|
||||
|
||||
|
||||
class BaseSelectEntity(SelectEntity, EcoFlowBaseCommandEntity):
|
||||
pass
|
||||
|
||||
|
||||
class BaseButtonEntity(ButtonEntity, EcoFlowBaseCommandEntity):
|
||||
pass
|
||||
|
||||
17
config/custom_components/ecoflow_cloud/manifest.json
Normal file
17
config/custom_components/ecoflow_cloud/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"domain": "ecoflow_cloud",
|
||||
"name": "Ecoflow-Cloud",
|
||||
"codeowners": [
|
||||
"@tolwi"
|
||||
],
|
||||
"config_flow": true,
|
||||
"documentation": "https://github.com/tolwi/hassio-ecoflow-cloud",
|
||||
"iot_class": "cloud_push",
|
||||
"issue_tracker": "https://github.com/tolwi/hassio-ecoflow-cloud/issues",
|
||||
"requirements": [
|
||||
"paho-mqtt==1.6.1",
|
||||
"reactivex==4.0.4",
|
||||
"protobuf>=4.23.0"
|
||||
],
|
||||
"version": "0.13.3"
|
||||
}
|
||||
340
config/custom_components/ecoflow_cloud/mqtt/ecoflow_mqtt.py
Normal file
340
config/custom_components/ecoflow_cloud/mqtt/ecoflow_mqtt.py
Normal file
@@ -0,0 +1,340 @@
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
import ssl
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
import paho.mqtt.client as mqtt_client
|
||||
import requests
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, DOMAIN
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.util import utcnow
|
||||
from reactivex import Subject, Observable
|
||||
|
||||
from .proto import powerstream_pb2 as powerstream, ecopacket_pb2 as ecopacket
|
||||
from .utils import BoundFifoList
|
||||
from ..config.const import CONF_DEVICE_TYPE, CONF_DEVICE_ID, OPTS_REFRESH_PERIOD_SEC, EcoflowModel
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EcoflowException(Exception):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(args, kwargs)
|
||||
|
||||
|
||||
class EcoflowAuthentication:
|
||||
def __init__(self, ecoflow_username, ecoflow_password):
|
||||
self.ecoflow_username = ecoflow_username
|
||||
self.ecoflow_password = ecoflow_password
|
||||
self.user_id = None
|
||||
self.token = None
|
||||
self.mqtt_url = "mqtt.mqtt.com"
|
||||
self.mqtt_port = 8883
|
||||
self.mqtt_username = None
|
||||
self.mqtt_password = None
|
||||
|
||||
def authorize(self):
|
||||
url = "https://api.ecoflow.com/auth/login"
|
||||
headers = {"lang": "en_US", "content-type": "application/json"}
|
||||
data = {"email": self.ecoflow_username,
|
||||
"password": base64.b64encode(self.ecoflow_password.encode()).decode(),
|
||||
"scene": "IOT_APP",
|
||||
"userType": "ECOFLOW"}
|
||||
|
||||
_LOGGER.info(f"Login to EcoFlow API {url}")
|
||||
request = requests.post(url, json=data, headers=headers)
|
||||
response = self.get_json_response(request)
|
||||
|
||||
try:
|
||||
self.token = response["data"]["token"]
|
||||
self.user_id = response["data"]["user"]["userId"]
|
||||
user_name = response["data"]["user"].get("name", "<no user name>")
|
||||
except KeyError as key:
|
||||
raise EcoflowException(f"Failed to extract key {key} from response: {response}")
|
||||
|
||||
_LOGGER.info(f"Successfully logged in: {user_name}")
|
||||
|
||||
url = "https://api.ecoflow.com/iot-auth/app/certification"
|
||||
headers = {"lang": "en_US", "authorization": f"Bearer {self.token}"}
|
||||
data = {"userId": self.user_id}
|
||||
|
||||
_LOGGER.info(f"Requesting IoT MQTT credentials {url}")
|
||||
request = requests.get(url, data=data, headers=headers)
|
||||
response = self.get_json_response(request)
|
||||
|
||||
try:
|
||||
self.mqtt_url = response["data"]["url"]
|
||||
self.mqtt_port = int(response["data"]["port"])
|
||||
self.mqtt_username = response["data"]["certificateAccount"]
|
||||
self.mqtt_password = response["data"]["certificatePassword"]
|
||||
except KeyError as key:
|
||||
raise EcoflowException(f"Failed to extract key {key} from {response}")
|
||||
|
||||
_LOGGER.info(f"Successfully extracted account: {self.mqtt_username}")
|
||||
|
||||
def get_json_response(self, request):
|
||||
if request.status_code != 200:
|
||||
raise EcoflowException(f"Got HTTP status code {request.status_code}: {request.text}")
|
||||
|
||||
try:
|
||||
response = json.loads(request.text)
|
||||
response_message = response["message"]
|
||||
except KeyError as key:
|
||||
raise EcoflowException(f"Failed to extract key {key} from {response}")
|
||||
except Exception as error:
|
||||
raise EcoflowException(f"Failed to parse response: {request.text} Error: {error}")
|
||||
|
||||
if response_message.lower() != "success":
|
||||
raise EcoflowException(f"{response_message}")
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class EcoflowDataHolder:
|
||||
def __init__(self, update_period_sec: int, collect_raw: bool = False):
|
||||
self.__update_period_sec = update_period_sec
|
||||
self.__collect_raw = collect_raw
|
||||
self.set = BoundFifoList[dict[str, Any]]()
|
||||
self.set_reply = BoundFifoList[dict[str, Any]]()
|
||||
self.get = BoundFifoList[dict[str, Any]]()
|
||||
self.get_reply = BoundFifoList[dict[str, Any]]()
|
||||
self.params = dict[str, Any]()
|
||||
|
||||
self.raw_data = BoundFifoList[dict[str, Any]]()
|
||||
|
||||
self.__params_time = utcnow().replace(year=2000, month=1, day=1, hour=0, minute=0, second=0)
|
||||
self.__params_broadcast_time = utcnow().replace(year=2000, month=1, day=1, hour=0, minute=0, second=0)
|
||||
self.__params_observable = Subject[dict[str, Any]]()
|
||||
|
||||
self.__set_reply_observable = Subject[list[dict[str, Any]]]()
|
||||
self.__get_reply_observable = Subject[list[dict[str, Any]]]()
|
||||
|
||||
def params_observable(self) -> Observable[dict[str, Any]]:
|
||||
return self.__params_observable
|
||||
|
||||
def get_reply_observable(self) -> Observable[list[dict[str, Any]]]:
|
||||
return self.__get_reply_observable
|
||||
|
||||
def set_reply_observable(self) -> Observable[list[dict[str, Any]]]:
|
||||
return self.__set_reply_observable
|
||||
|
||||
def add_set_message(self, msg: dict[str, Any]):
|
||||
self.set.append(msg)
|
||||
|
||||
def add_set_reply_message(self, msg: dict[str, Any]):
|
||||
self.set_reply.append(msg)
|
||||
self.__set_reply_observable.on_next(self.set_reply)
|
||||
|
||||
def add_get_message(self, msg: dict[str, Any]):
|
||||
self.get.append(msg)
|
||||
|
||||
def add_get_reply_message(self, msg: dict[str, Any]):
|
||||
self.get_reply.append(msg)
|
||||
self.__get_reply_observable.on_next(self.get_reply)
|
||||
|
||||
def update_to_target_state(self, target_state: dict[str, Any]):
|
||||
self.params.update(target_state)
|
||||
self.__broadcast()
|
||||
|
||||
def update_data(self, raw: dict[str, Any]):
|
||||
self.__add_raw_data(raw)
|
||||
# self.__params_time = datetime.fromtimestamp(raw['timestamp'], UTC)
|
||||
self.__params_time = utcnow()
|
||||
self.params['timestamp'] = raw['timestamp']
|
||||
self.params.update(raw['params'])
|
||||
|
||||
if (utcnow() - self.__params_broadcast_time).total_seconds() > self.__update_period_sec:
|
||||
self.__broadcast()
|
||||
|
||||
def __broadcast(self):
|
||||
self.__params_broadcast_time = utcnow()
|
||||
self.__params_observable.on_next(self.params)
|
||||
|
||||
def __add_raw_data(self, raw: dict[str, Any]):
|
||||
if self.__collect_raw:
|
||||
self.raw_data.append(raw)
|
||||
|
||||
def params_time(self) -> datetime:
|
||||
return self.__params_time
|
||||
|
||||
|
||||
class EcoflowMQTTClient:
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry, auth: EcoflowAuthentication):
|
||||
|
||||
self.auth = auth
|
||||
self.config_entry = entry
|
||||
self.device_type = entry.data[CONF_DEVICE_TYPE]
|
||||
self.device_sn = entry.data[CONF_DEVICE_ID]
|
||||
|
||||
self._data_topic = f"/app/device/property/{self.device_sn}"
|
||||
self._set_topic = f"/app/{auth.user_id}/{self.device_sn}/thing/property/set"
|
||||
self._set_reply_topic = f"/app/{auth.user_id}/{self.device_sn}/thing/property/set_reply"
|
||||
self._get_topic = f"/app/{auth.user_id}/{self.device_sn}/thing/property/get"
|
||||
self._get_reply_topic = f"/app/{auth.user_id}/{self.device_sn}/thing/property/get_reply"
|
||||
|
||||
self.data = EcoflowDataHolder(entry.options.get(OPTS_REFRESH_PERIOD_SEC), self.device_type == "DIAGNOSTIC")
|
||||
|
||||
self.device_info_main = DeviceInfo(
|
||||
identifiers={(DOMAIN, self.device_sn)},
|
||||
manufacturer="EcoFlow",
|
||||
name=entry.title,
|
||||
model=self.device_type,
|
||||
)
|
||||
|
||||
self.client = mqtt_client.Client(client_id=f'ANDROID_-{str(uuid.uuid4()).upper()}_{auth.user_id}',
|
||||
clean_session=True, reconnect_on_failure=True)
|
||||
self.client.username_pw_set(self.auth.mqtt_username, self.auth.mqtt_password)
|
||||
self.client.tls_set(certfile=None, keyfile=None, cert_reqs=ssl.CERT_REQUIRED)
|
||||
self.client.tls_insecure_set(False)
|
||||
self.client.on_connect = self.on_connect
|
||||
self.client.on_disconnect = self.on_disconnect
|
||||
if self.device_type == EcoflowModel.POWERSTREAM.name:
|
||||
self.client.on_message = self.on_bytes_message
|
||||
else:
|
||||
self.client.on_message = self.on_json_message
|
||||
|
||||
_LOGGER.info(f"Connecting to MQTT Broker {self.auth.mqtt_url}:{self.auth.mqtt_port}")
|
||||
self.client.connect(self.auth.mqtt_url, self.auth.mqtt_port, 30)
|
||||
self.client.loop_start()
|
||||
|
||||
def is_connected(self):
|
||||
return self.client.is_connected()
|
||||
|
||||
def reconnect(self) -> bool:
|
||||
try:
|
||||
_LOGGER.info(f"Re-connecting to MQTT Broker {self.auth.mqtt_url}:{self.auth.mqtt_port}")
|
||||
self.client.loop_stop(True)
|
||||
self.client.reconnect()
|
||||
self.client.loop_start()
|
||||
return True
|
||||
except Exception as e:
|
||||
_LOGGER.error(e)
|
||||
return False
|
||||
|
||||
def on_connect(self, client, userdata, flags, rc):
|
||||
match rc:
|
||||
case 0:
|
||||
self.client.subscribe([(self._data_topic, 1),
|
||||
(self._set_topic, 1), (self._set_reply_topic, 1),
|
||||
(self._get_topic, 1), (self._get_reply_topic, 1)])
|
||||
_LOGGER.info(f"Subscribed to MQTT topic {self._data_topic}")
|
||||
case -1:
|
||||
_LOGGER.error("Failed to connect to MQTT: connection timed out")
|
||||
case 1:
|
||||
_LOGGER.error("Failed to connect to MQTT: incorrect protocol version")
|
||||
case 2:
|
||||
_LOGGER.error("Failed to connect to MQTT: invalid client identifier")
|
||||
case 3:
|
||||
_LOGGER.error("Failed to connect to MQTT: server unavailable")
|
||||
case 4:
|
||||
_LOGGER.error("Failed to connect to MQTT: bad username or password")
|
||||
case 5:
|
||||
_LOGGER.error("Failed to connect to MQTT: not authorised")
|
||||
case _:
|
||||
_LOGGER.error(f"Failed to connect to MQTT: another error occured: {rc}")
|
||||
|
||||
return client
|
||||
|
||||
def on_disconnect(self, client, userdata, rc):
|
||||
if rc != 0:
|
||||
_LOGGER.error(f"Unexpected MQTT disconnection: {rc}. Will auto-reconnect")
|
||||
time.sleep(5)
|
||||
# self.client.reconnect() ??
|
||||
|
||||
def on_json_message(self, client, userdata, message):
|
||||
try:
|
||||
payload = message.payload.decode("utf-8", errors='ignore')
|
||||
raw = json.loads(payload)
|
||||
|
||||
if message.topic == self._data_topic:
|
||||
self.data.update_data(raw)
|
||||
elif message.topic == self._set_topic:
|
||||
self.data.add_set_message(raw)
|
||||
elif message.topic == self._set_reply_topic:
|
||||
self.data.add_set_reply_message(raw)
|
||||
elif message.topic == self._get_topic:
|
||||
self.data.add_get_message(raw)
|
||||
elif message.topic == self._get_reply_topic:
|
||||
self.data.add_get_reply_message(raw)
|
||||
except UnicodeDecodeError as error:
|
||||
_LOGGER.error(f"UnicodeDecodeError: {error}. Ignoring message and waiting for the next one.")
|
||||
|
||||
def on_bytes_message(self, client, userdata, message):
|
||||
try:
|
||||
payload = message.payload
|
||||
|
||||
while True:
|
||||
packet = ecopacket.SendHeaderMsg()
|
||||
packet.ParseFromString(payload)
|
||||
|
||||
_LOGGER.debug("cmd id %u payload \"%s\"", packet.msg.cmd_id, payload.hex())
|
||||
|
||||
if packet.msg.cmd_id != 1:
|
||||
_LOGGER.info("Unsupported EcoPacket cmd id %u", packet.msg.cmd_id)
|
||||
|
||||
else:
|
||||
heartbeat = powerstream.InverterHeartbeat()
|
||||
heartbeat.ParseFromString(packet.msg.pdata)
|
||||
|
||||
raw = {"params": {}}
|
||||
|
||||
for descriptor in heartbeat.DESCRIPTOR.fields:
|
||||
if not heartbeat.HasField(descriptor.name):
|
||||
continue
|
||||
|
||||
raw["params"][descriptor.name] = getattr(heartbeat, descriptor.name)
|
||||
|
||||
_LOGGER.info("Found %u fields", len(raw["params"]))
|
||||
|
||||
raw["timestamp"] = utcnow()
|
||||
|
||||
self.data.update_data(raw)
|
||||
|
||||
if packet.ByteSize() >= len(payload):
|
||||
break
|
||||
|
||||
_LOGGER.info("Found another frame in payload")
|
||||
|
||||
packetLength = len(payload) - packet.ByteSize()
|
||||
payload = payload[:packetLength]
|
||||
|
||||
except Exception as error:
|
||||
_LOGGER.error(error)
|
||||
_LOGGER.info(message.payload.hex())
|
||||
|
||||
message_id = 999900000 + random.randint(10000, 99999)
|
||||
|
||||
def __prepare_payload(self, command: dict):
|
||||
self.message_id += 1
|
||||
payload = {"from": "HomeAssistant",
|
||||
"id": f"{self.message_id}",
|
||||
"version": "1.0"}
|
||||
payload.update(command)
|
||||
return payload
|
||||
|
||||
def __send(self, topic: str, message: str):
|
||||
try:
|
||||
info = self.client.publish(topic, message, 1)
|
||||
_LOGGER.debug("Sending " + message + " :" + str(info) + "(" + str(info.is_published()) + ")")
|
||||
except RuntimeError as error:
|
||||
_LOGGER.error(error)
|
||||
|
||||
def send_get_message(self, command: dict):
|
||||
payload = self.__prepare_payload(command)
|
||||
self.__send(self._get_topic, json.dumps(payload))
|
||||
|
||||
def send_set_message(self, mqtt_state: dict[str, Any], command: dict):
|
||||
self.data.update_to_target_state(mqtt_state)
|
||||
payload = self.__prepare_payload(command)
|
||||
self.__send(self._set_topic, json.dumps(payload))
|
||||
|
||||
def stop(self):
|
||||
self.client.loop_stop()
|
||||
self.client.disconnect()
|
||||
@@ -0,0 +1,57 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message Header
|
||||
{
|
||||
optional bytes pdata = 1;
|
||||
optional int32 src = 2;
|
||||
optional int32 dest = 3;
|
||||
optional int32 d_src= 4;
|
||||
optional int32 d_dest = 5;
|
||||
optional int32 enc_type = 6;
|
||||
optional int32 check_type = 7;
|
||||
optional int32 cmd_func = 8;
|
||||
optional int32 cmd_id = 9;
|
||||
optional int32 data_len = 10;
|
||||
optional int32 need_ack = 11;
|
||||
optional int32 is_ack = 12;
|
||||
optional int32 seq = 14;
|
||||
optional int32 product_id = 15;
|
||||
optional int32 version = 16;
|
||||
optional int32 payload_ver = 17;
|
||||
optional int32 time_snap = 18;
|
||||
optional int32 is_rw_cmd = 19;
|
||||
optional int32 is_queue = 20;
|
||||
optional int32 ack_type= 21;
|
||||
optional string code = 22;
|
||||
optional string from = 23;
|
||||
optional string module_sn = 24;
|
||||
optional string device_sn = 25;
|
||||
}
|
||||
|
||||
message SendHeaderMsg
|
||||
{
|
||||
optional Header msg = 1;
|
||||
}
|
||||
|
||||
message SendMsgHart
|
||||
{
|
||||
optional int32 link_id = 1;
|
||||
optional int32 src = 2;
|
||||
optional int32 dest = 3;
|
||||
optional int32 d_src = 4;
|
||||
optional int32 d_dest = 5;
|
||||
optional int32 enc_type = 6;
|
||||
optional int32 check_type = 7;
|
||||
optional int32 cmd_func = 8;
|
||||
optional int32 cmd_id = 9;
|
||||
optional int32 data_len = 10;
|
||||
optional int32 need_ack = 11;
|
||||
optional int32 is_ack = 12;
|
||||
optional int32 ack_type = 13;
|
||||
optional int32 seq = 14;
|
||||
optional int32 time_snap = 15;
|
||||
optional int32 is_rw_cmd = 16;
|
||||
optional int32 is_queue = 17;
|
||||
optional int32 product_id = 18;
|
||||
optional int32 version = 19;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: ecopacket.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0f\x65\x63opacket.proto\"\xb8\x06\n\x06Header\x12\x12\n\x05pdata\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x10\n\x03src\x18\x02 \x01(\x05H\x01\x88\x01\x01\x12\x11\n\x04\x64\x65st\x18\x03 \x01(\x05H\x02\x88\x01\x01\x12\x12\n\x05\x64_src\x18\x04 \x01(\x05H\x03\x88\x01\x01\x12\x13\n\x06\x64_dest\x18\x05 \x01(\x05H\x04\x88\x01\x01\x12\x15\n\x08\x65nc_type\x18\x06 \x01(\x05H\x05\x88\x01\x01\x12\x17\n\ncheck_type\x18\x07 \x01(\x05H\x06\x88\x01\x01\x12\x15\n\x08\x63md_func\x18\x08 \x01(\x05H\x07\x88\x01\x01\x12\x13\n\x06\x63md_id\x18\t \x01(\x05H\x08\x88\x01\x01\x12\x15\n\x08\x64\x61ta_len\x18\n \x01(\x05H\t\x88\x01\x01\x12\x15\n\x08need_ack\x18\x0b \x01(\x05H\n\x88\x01\x01\x12\x13\n\x06is_ack\x18\x0c \x01(\x05H\x0b\x88\x01\x01\x12\x10\n\x03seq\x18\x0e \x01(\x05H\x0c\x88\x01\x01\x12\x17\n\nproduct_id\x18\x0f \x01(\x05H\r\x88\x01\x01\x12\x14\n\x07version\x18\x10 \x01(\x05H\x0e\x88\x01\x01\x12\x18\n\x0bpayload_ver\x18\x11 \x01(\x05H\x0f\x88\x01\x01\x12\x16\n\ttime_snap\x18\x12 \x01(\x05H\x10\x88\x01\x01\x12\x16\n\tis_rw_cmd\x18\x13 \x01(\x05H\x11\x88\x01\x01\x12\x15\n\x08is_queue\x18\x14 \x01(\x05H\x12\x88\x01\x01\x12\x15\n\x08\x61\x63k_type\x18\x15 \x01(\x05H\x13\x88\x01\x01\x12\x11\n\x04\x63ode\x18\x16 \x01(\tH\x14\x88\x01\x01\x12\x11\n\x04\x66rom\x18\x17 \x01(\tH\x15\x88\x01\x01\x12\x16\n\tmodule_sn\x18\x18 \x01(\tH\x16\x88\x01\x01\x12\x16\n\tdevice_sn\x18\x19 \x01(\tH\x17\x88\x01\x01\x42\x08\n\x06_pdataB\x06\n\x04_srcB\x07\n\x05_destB\x08\n\x06_d_srcB\t\n\x07_d_destB\x0b\n\t_enc_typeB\r\n\x0b_check_typeB\x0b\n\t_cmd_funcB\t\n\x07_cmd_idB\x0b\n\t_data_lenB\x0b\n\t_need_ackB\t\n\x07_is_ackB\x06\n\x04_seqB\r\n\x0b_product_idB\n\n\x08_versionB\x0e\n\x0c_payload_verB\x0c\n\n_time_snapB\x0c\n\n_is_rw_cmdB\x0b\n\t_is_queueB\x0b\n\t_ack_typeB\x07\n\x05_codeB\x07\n\x05_fromB\x0c\n\n_module_snB\x0c\n\n_device_sn\"2\n\rSendHeaderMsg\x12\x19\n\x03msg\x18\x01 \x01(\x0b\x32\x07.HeaderH\x00\x88\x01\x01\x42\x06\n\x04_msg\"\x93\x05\n\x0bSendMsgHart\x12\x14\n\x07link_id\x18\x01 \x01(\x05H\x00\x88\x01\x01\x12\x10\n\x03src\x18\x02 \x01(\x05H\x01\x88\x01\x01\x12\x11\n\x04\x64\x65st\x18\x03 \x01(\x05H\x02\x88\x01\x01\x12\x12\n\x05\x64_src\x18\x04 \x01(\x05H\x03\x88\x01\x01\x12\x13\n\x06\x64_dest\x18\x05 \x01(\x05H\x04\x88\x01\x01\x12\x15\n\x08\x65nc_type\x18\x06 \x01(\x05H\x05\x88\x01\x01\x12\x17\n\ncheck_type\x18\x07 \x01(\x05H\x06\x88\x01\x01\x12\x15\n\x08\x63md_func\x18\x08 \x01(\x05H\x07\x88\x01\x01\x12\x13\n\x06\x63md_id\x18\t \x01(\x05H\x08\x88\x01\x01\x12\x15\n\x08\x64\x61ta_len\x18\n \x01(\x05H\t\x88\x01\x01\x12\x15\n\x08need_ack\x18\x0b \x01(\x05H\n\x88\x01\x01\x12\x13\n\x06is_ack\x18\x0c \x01(\x05H\x0b\x88\x01\x01\x12\x15\n\x08\x61\x63k_type\x18\r \x01(\x05H\x0c\x88\x01\x01\x12\x10\n\x03seq\x18\x0e \x01(\x05H\r\x88\x01\x01\x12\x16\n\ttime_snap\x18\x0f \x01(\x05H\x0e\x88\x01\x01\x12\x16\n\tis_rw_cmd\x18\x10 \x01(\x05H\x0f\x88\x01\x01\x12\x15\n\x08is_queue\x18\x11 \x01(\x05H\x10\x88\x01\x01\x12\x17\n\nproduct_id\x18\x12 \x01(\x05H\x11\x88\x01\x01\x12\x14\n\x07version\x18\x13 \x01(\x05H\x12\x88\x01\x01\x42\n\n\x08_link_idB\x06\n\x04_srcB\x07\n\x05_destB\x08\n\x06_d_srcB\t\n\x07_d_destB\x0b\n\t_enc_typeB\r\n\x0b_check_typeB\x0b\n\t_cmd_funcB\t\n\x07_cmd_idB\x0b\n\t_data_lenB\x0b\n\t_need_ackB\t\n\x07_is_ackB\x0b\n\t_ack_typeB\x06\n\x04_seqB\x0c\n\n_time_snapB\x0c\n\n_is_rw_cmdB\x0b\n\t_is_queueB\r\n\x0b_product_idB\n\n\x08_versionb\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ecopacket_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
_globals['_HEADER']._serialized_start=20
|
||||
_globals['_HEADER']._serialized_end=844
|
||||
_globals['_SENDHEADERMSG']._serialized_start=846
|
||||
_globals['_SENDHEADERMSG']._serialized_end=896
|
||||
_globals['_SENDMSGHART']._serialized_start=899
|
||||
_globals['_SENDMSGHART']._serialized_end=1558
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
105
config/custom_components/ecoflow_cloud/mqtt/proto/platform.proto
Normal file
105
config/custom_components/ecoflow_cloud/mqtt/proto/platform.proto
Normal file
@@ -0,0 +1,105 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message EnergyItem
|
||||
{
|
||||
optional uint32 timestamp = 1;
|
||||
optional uint32 watth_type = 2;
|
||||
repeated uint32 watth = 3;
|
||||
}
|
||||
|
||||
message EnergyTotalReport
|
||||
{
|
||||
optional uint32 watth_seq = 1;
|
||||
optional EnergyItem watth_item = 2;
|
||||
}
|
||||
|
||||
message BatchEnergyTotalReport
|
||||
{
|
||||
optional uint32 watth_seq = 1;
|
||||
repeated EnergyItem watth_item = 2;
|
||||
}
|
||||
|
||||
message EnergyTotalReportAck
|
||||
{
|
||||
optional uint32 result = 1;
|
||||
optional uint32 watth_seq = 2;
|
||||
optional uint32 watth_type = 3;
|
||||
}
|
||||
|
||||
message EventRecordItem
|
||||
{
|
||||
optional uint32 timestamp = 1;
|
||||
optional uint32 sys_ms = 2;
|
||||
optional uint32 event_no = 3;
|
||||
repeated float event_detail = 4;
|
||||
}
|
||||
|
||||
message EventRecordReport
|
||||
{
|
||||
optional uint32 event_ver = 1;
|
||||
optional uint32 event_seq = 2;
|
||||
repeated EventRecordItem event_item = 3;
|
||||
}
|
||||
|
||||
message EventInfoReportAck
|
||||
{
|
||||
optional uint32 result = 1;
|
||||
optional uint32 event_seq = 2;
|
||||
optional uint32 event_item_num =3;
|
||||
}
|
||||
|
||||
message ProductNameSet
|
||||
{
|
||||
optional string name = 1;
|
||||
}
|
||||
|
||||
message ProductNameSetAck
|
||||
{
|
||||
optional uint32 result = 1;
|
||||
}
|
||||
|
||||
message ProductNameGet { }
|
||||
|
||||
message ProductNameGetAck
|
||||
{
|
||||
optional string name = 3;
|
||||
}
|
||||
|
||||
message RTCTimeGet { }
|
||||
|
||||
message RTCTimeGetAck
|
||||
{
|
||||
optional uint32 timestamp = 1;
|
||||
optional int32 timezone = 2;
|
||||
}
|
||||
|
||||
message RTCTimeSet
|
||||
{
|
||||
optional uint32 timestamp = 1;
|
||||
optional int32 timezone = 2;
|
||||
}
|
||||
|
||||
message RTCTimeSetAck
|
||||
{
|
||||
optional uint32 result = 1;
|
||||
}
|
||||
|
||||
message country_town_message
|
||||
{
|
||||
optional uint32 country = 1;
|
||||
optional uint32 town = 2;
|
||||
}
|
||||
|
||||
enum PlCmdSets
|
||||
{
|
||||
PL_NONE_CMD_SETS = 0;
|
||||
PL_BASIC_CMD_SETS = 1;
|
||||
PL_EXT_CMD_SETS = 254;
|
||||
}
|
||||
|
||||
enum PlCmdId
|
||||
{
|
||||
PL_CMD_ID_NONE = 0;
|
||||
PL_CMD_ID_XLOG = 16;
|
||||
PL_CMD_ID_WATTH = 32;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: platform.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eplatform.proto\"i\n\nEnergyItem\x12\x16\n\ttimestamp\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x17\n\nwatth_type\x18\x02 \x01(\rH\x01\x88\x01\x01\x12\r\n\x05watth\x18\x03 \x03(\rB\x0c\n\n_timestampB\r\n\x0b_watth_type\"n\n\x11\x45nergyTotalReport\x12\x16\n\twatth_seq\x18\x01 \x01(\rH\x00\x88\x01\x01\x12$\n\nwatth_item\x18\x02 \x01(\x0b\x32\x0b.EnergyItemH\x01\x88\x01\x01\x42\x0c\n\n_watth_seqB\r\n\x0b_watth_item\"_\n\x16\x42\x61tchEnergyTotalReport\x12\x16\n\twatth_seq\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x1f\n\nwatth_item\x18\x02 \x03(\x0b\x32\x0b.EnergyItemB\x0c\n\n_watth_seq\"\x84\x01\n\x14\x45nergyTotalReportAck\x12\x13\n\x06result\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x16\n\twatth_seq\x18\x02 \x01(\rH\x01\x88\x01\x01\x12\x17\n\nwatth_type\x18\x03 \x01(\rH\x02\x88\x01\x01\x42\t\n\x07_resultB\x0c\n\n_watth_seqB\r\n\x0b_watth_type\"\x91\x01\n\x0f\x45ventRecordItem\x12\x16\n\ttimestamp\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x13\n\x06sys_ms\x18\x02 \x01(\rH\x01\x88\x01\x01\x12\x15\n\x08\x65vent_no\x18\x03 \x01(\rH\x02\x88\x01\x01\x12\x14\n\x0c\x65vent_detail\x18\x04 \x03(\x02\x42\x0c\n\n_timestampB\t\n\x07_sys_msB\x0b\n\t_event_no\"\x85\x01\n\x11\x45ventRecordReport\x12\x16\n\tevent_ver\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x16\n\tevent_seq\x18\x02 \x01(\rH\x01\x88\x01\x01\x12$\n\nevent_item\x18\x03 \x03(\x0b\x32\x10.EventRecordItemB\x0c\n\n_event_verB\x0c\n\n_event_seq\"\x8a\x01\n\x12\x45ventInfoReportAck\x12\x13\n\x06result\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x16\n\tevent_seq\x18\x02 \x01(\rH\x01\x88\x01\x01\x12\x1b\n\x0e\x65vent_item_num\x18\x03 \x01(\rH\x02\x88\x01\x01\x42\t\n\x07_resultB\x0c\n\n_event_seqB\x11\n\x0f_event_item_num\",\n\x0eProductNameSet\x12\x11\n\x04name\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x07\n\x05_name\"3\n\x11ProductNameSetAck\x12\x13\n\x06result\x18\x01 \x01(\rH\x00\x88\x01\x01\x42\t\n\x07_result\"\x10\n\x0eProductNameGet\"/\n\x11ProductNameGetAck\x12\x11\n\x04name\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x07\n\x05_name\"\x0c\n\nRTCTimeGet\"Y\n\rRTCTimeGetAck\x12\x16\n\ttimestamp\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x15\n\x08timezone\x18\x02 \x01(\x05H\x01\x88\x01\x01\x42\x0c\n\n_timestampB\x0b\n\t_timezone\"V\n\nRTCTimeSet\x12\x16\n\ttimestamp\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x15\n\x08timezone\x18\x02 \x01(\x05H\x01\x88\x01\x01\x42\x0c\n\n_timestampB\x0b\n\t_timezone\"/\n\rRTCTimeSetAck\x12\x13\n\x06result\x18\x01 \x01(\rH\x00\x88\x01\x01\x42\t\n\x07_result\"T\n\x14\x63ountry_town_message\x12\x14\n\x07\x63ountry\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x11\n\x04town\x18\x02 \x01(\rH\x01\x88\x01\x01\x42\n\n\x08_countryB\x07\n\x05_town*N\n\tPlCmdSets\x12\x14\n\x10PL_NONE_CMD_SETS\x10\x00\x12\x15\n\x11PL_BASIC_CMD_SETS\x10\x01\x12\x14\n\x0fPL_EXT_CMD_SETS\x10\xfe\x01*F\n\x07PlCmdId\x12\x12\n\x0ePL_CMD_ID_NONE\x10\x00\x12\x12\n\x0ePL_CMD_ID_XLOG\x10\x10\x12\x13\n\x0fPL_CMD_ID_WATTH\x10 b\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'platform_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
_globals['_PLCMDSETS']._serialized_start=1388
|
||||
_globals['_PLCMDSETS']._serialized_end=1466
|
||||
_globals['_PLCMDID']._serialized_start=1468
|
||||
_globals['_PLCMDID']._serialized_end=1538
|
||||
_globals['_ENERGYITEM']._serialized_start=18
|
||||
_globals['_ENERGYITEM']._serialized_end=123
|
||||
_globals['_ENERGYTOTALREPORT']._serialized_start=125
|
||||
_globals['_ENERGYTOTALREPORT']._serialized_end=235
|
||||
_globals['_BATCHENERGYTOTALREPORT']._serialized_start=237
|
||||
_globals['_BATCHENERGYTOTALREPORT']._serialized_end=332
|
||||
_globals['_ENERGYTOTALREPORTACK']._serialized_start=335
|
||||
_globals['_ENERGYTOTALREPORTACK']._serialized_end=467
|
||||
_globals['_EVENTRECORDITEM']._serialized_start=470
|
||||
_globals['_EVENTRECORDITEM']._serialized_end=615
|
||||
_globals['_EVENTRECORDREPORT']._serialized_start=618
|
||||
_globals['_EVENTRECORDREPORT']._serialized_end=751
|
||||
_globals['_EVENTINFOREPORTACK']._serialized_start=754
|
||||
_globals['_EVENTINFOREPORTACK']._serialized_end=892
|
||||
_globals['_PRODUCTNAMESET']._serialized_start=894
|
||||
_globals['_PRODUCTNAMESET']._serialized_end=938
|
||||
_globals['_PRODUCTNAMESETACK']._serialized_start=940
|
||||
_globals['_PRODUCTNAMESETACK']._serialized_end=991
|
||||
_globals['_PRODUCTNAMEGET']._serialized_start=993
|
||||
_globals['_PRODUCTNAMEGET']._serialized_end=1009
|
||||
_globals['_PRODUCTNAMEGETACK']._serialized_start=1011
|
||||
_globals['_PRODUCTNAMEGETACK']._serialized_end=1058
|
||||
_globals['_RTCTIMEGET']._serialized_start=1060
|
||||
_globals['_RTCTIMEGET']._serialized_end=1072
|
||||
_globals['_RTCTIMEGETACK']._serialized_start=1074
|
||||
_globals['_RTCTIMEGETACK']._serialized_end=1163
|
||||
_globals['_RTCTIMESET']._serialized_start=1165
|
||||
_globals['_RTCTIMESET']._serialized_end=1251
|
||||
_globals['_RTCTIMESETACK']._serialized_start=1253
|
||||
_globals['_RTCTIMESETACK']._serialized_end=1300
|
||||
_globals['_COUNTRY_TOWN_MESSAGE']._serialized_start=1302
|
||||
_globals['_COUNTRY_TOWN_MESSAGE']._serialized_end=1386
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -0,0 +1,127 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message InverterHeartbeat {
|
||||
optional uint32 inv_error_code = 1;
|
||||
optional uint32 inv_warning_code = 3;
|
||||
optional uint32 pv1_error_code = 2;
|
||||
optional uint32 pv1_warning_code = 4;
|
||||
optional uint32 pv2_error_code = 5;
|
||||
optional uint32 pv2_warning_code = 6;
|
||||
optional uint32 bat_error_code = 7;
|
||||
optional uint32 bat_warning_code = 8;
|
||||
optional uint32 llc_error_code = 9;
|
||||
optional uint32 llc_warning_code = 10;
|
||||
optional uint32 pv1_status = 11;
|
||||
optional uint32 pv2_status = 12;
|
||||
optional uint32 bat_status = 13;
|
||||
optional uint32 llc_status = 14;
|
||||
optional uint32 inv_status = 15;
|
||||
optional int32 pv1_input_volt = 16;
|
||||
optional int32 pv1_op_volt = 17;
|
||||
optional int32 pv1_input_cur = 18;
|
||||
optional int32 pv1_input_watts = 19;
|
||||
optional int32 pv1_temp = 20;
|
||||
optional int32 pv2_input_volt = 21;
|
||||
optional int32 pv2_op_volt = 22;
|
||||
optional int32 pv2_input_cur = 23;
|
||||
optional int32 pv2_input_watts = 24;
|
||||
optional int32 pv2_temp = 25;
|
||||
optional int32 bat_input_volt = 26;
|
||||
optional int32 bat_op_volt = 27;
|
||||
optional int32 bat_input_cur = 28;
|
||||
optional int32 bat_input_watts = 29;
|
||||
optional int32 bat_temp = 30;
|
||||
optional uint32 bat_soc = 31;
|
||||
optional int32 llc_input_volt = 32;
|
||||
optional int32 llc_op_volt = 33;
|
||||
optional int32 llc_temp = 34;
|
||||
optional int32 inv_input_volt = 35;
|
||||
optional int32 inv_op_volt = 36;
|
||||
optional int32 inv_output_cur = 37;
|
||||
optional int32 inv_output_watts = 38;
|
||||
optional int32 inv_temp = 39;
|
||||
optional int32 inv_freq = 40;
|
||||
optional int32 inv_dc_cur = 41;
|
||||
optional int32 bp_type = 42;
|
||||
optional int32 inv_relay_status = 43;
|
||||
optional int32 pv1_relay_status = 44;
|
||||
optional int32 pv2_relay_status = 45;
|
||||
optional uint32 install_country = 46;
|
||||
optional uint32 install_town = 47;
|
||||
optional uint32 permanent_watts = 48;
|
||||
optional uint32 dynamic_watts = 49;
|
||||
optional uint32 supply_priority = 50;
|
||||
optional uint32 lower_limit = 51;
|
||||
optional uint32 upper_limit = 52;
|
||||
optional uint32 inv_on_off = 53;
|
||||
optional uint32 wireless_error_code = 54;
|
||||
optional uint32 wireless_warning_code = 55;
|
||||
optional uint32 inv_brightness = 56;
|
||||
optional uint32 heartbeat_frequency = 57;
|
||||
optional uint32 rated_power = 58;
|
||||
optional uint32 battery_charge_remain = 59;
|
||||
optional uint32 battery_discharge_remain = 60;
|
||||
}
|
||||
|
||||
message PermanentWattsPack
|
||||
{
|
||||
optional uint32 permanent_watts = 1;
|
||||
}
|
||||
|
||||
message SupplyPriorityPack
|
||||
{
|
||||
optional uint32 supply_priority = 1;
|
||||
}
|
||||
|
||||
message BatLowerPack
|
||||
{
|
||||
optional int32 lower_limit = 1;
|
||||
}
|
||||
|
||||
message BatUpperPack
|
||||
{
|
||||
optional int32 upper_limit = 1;
|
||||
}
|
||||
|
||||
message BrightnessPack
|
||||
{
|
||||
optional int32 brightness = 1;
|
||||
}
|
||||
|
||||
message PowerItem
|
||||
{
|
||||
optional uint32 timestamp = 1;
|
||||
optional sint32 timezone = 2;
|
||||
optional uint32 inv_to_grid_power = 3;
|
||||
optional uint32 inv_to_plug_power = 4;
|
||||
optional int32 battery_power = 5;
|
||||
optional uint32 pv1_output_power = 6;
|
||||
optional uint32 pv2_output_power = 7;
|
||||
}
|
||||
|
||||
message PowerPack
|
||||
{
|
||||
optional uint32 sys_seq = 1;
|
||||
repeated PowerItem sys_power_stream = 2;
|
||||
}
|
||||
|
||||
message PowerAckPack
|
||||
{
|
||||
optional uint32 sys_seq = 1;
|
||||
}
|
||||
|
||||
message NodeMassage
|
||||
{
|
||||
optional string sn = 1;
|
||||
optional bytes mac = 2;
|
||||
}
|
||||
|
||||
message MeshChildNodeInfo
|
||||
{
|
||||
optional uint32 topology_type = 1;
|
||||
optional uint32 mesh_protocol = 2;
|
||||
optional uint32 max_sub_device_num = 3;
|
||||
optional bytes parent_mac_id = 4;
|
||||
optional bytes mesh_id = 5;
|
||||
repeated NodeMassage sub_device_list = 6;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
34
config/custom_components/ecoflow_cloud/mqtt/utils.py
Normal file
34
config/custom_components/ecoflow_cloud/mqtt/utils.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from collections import OrderedDict
|
||||
from typing import Callable, List, TypeVar
|
||||
|
||||
|
||||
class LimitedSizeOrderedDict(OrderedDict):
|
||||
def __init__(self, maxlen=20):
|
||||
"""Initialize a new DedupStore."""
|
||||
super().__init__()
|
||||
self.maxlen = maxlen
|
||||
|
||||
def append(self, key, val, on_delete: Callable = None):
|
||||
self[key] = val
|
||||
self.move_to_end(key)
|
||||
if len(self) > self.maxlen:
|
||||
# Removes the first record which should also be the oldest
|
||||
itm = self.popitem(last=False)
|
||||
if on_delete:
|
||||
on_delete(itm)
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class BoundFifoList(List):
|
||||
|
||||
def __init__(self, maxlen=20) -> None:
|
||||
super().__init__()
|
||||
self.maxlen = maxlen
|
||||
|
||||
def append(self, __object: _T) -> None:
|
||||
super().insert(0, __object)
|
||||
while len(self) >= self.maxlen:
|
||||
self.pop()
|
||||
|
||||
86
config/custom_components/ecoflow_cloud/number.py
Normal file
86
config/custom_components/ecoflow_cloud/number.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from typing import Callable, Any
|
||||
|
||||
from homeassistant.components.number import NumberMode
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE, UnitOfPower, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import DOMAIN, OPTS_POWER_STEP
|
||||
from .entities import BaseNumberEntity
|
||||
from .mqtt.ecoflow_mqtt import EcoflowMQTTClient
|
||||
|
||||
|
||||
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].numbers(client))
|
||||
|
||||
|
||||
class ValueUpdateEntity(BaseNumberEntity):
|
||||
_attr_native_step = 1
|
||||
_attr_mode = NumberMode.SLIDER
|
||||
|
||||
async def async_set_native_value(self, value: float):
|
||||
if self._command:
|
||||
ival = int(value)
|
||||
self.send_set_message(ival, self.command_dict(ival))
|
||||
|
||||
|
||||
class ChargingPowerEntity(ValueUpdateEntity):
|
||||
_attr_icon = "mdi:transmission-tower-import"
|
||||
_attr_native_unit_of_measurement = UnitOfPower.WATT
|
||||
_attr_device_class = SensorDeviceClass.POWER
|
||||
|
||||
def __init__(self, client: EcoflowMQTTClient, mqtt_key: str, title: str, min_value: int, max_value: int,
|
||||
command: Callable[[int], dict[str, any]] | None, enabled: bool = True, auto_enable: bool = False):
|
||||
super().__init__(client, mqtt_key, title, min_value, max_value, command, enabled, auto_enable)
|
||||
|
||||
self._attr_native_step = client.config_entry.options[OPTS_POWER_STEP]
|
||||
|
||||
|
||||
class BatteryBackupLevel(ValueUpdateEntity):
|
||||
_attr_icon = "mdi:battery-charging-90"
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
|
||||
def __init__(self, client: EcoflowMQTTClient, mqtt_key: str, title: str,
|
||||
min_value: int, max_value: int,
|
||||
min_key: str, max_key: str,
|
||||
command: Callable[[int], dict[str, any]] | None):
|
||||
super().__init__(client, mqtt_key, title, min_value, max_value, command, True, False)
|
||||
self.__min_key = min_key
|
||||
self.__max_key = max_key
|
||||
|
||||
def _updated(self, data: dict[str, Any]):
|
||||
if self.__min_key in data:
|
||||
self._attr_native_min_value = int(data[self.__min_key]) + 5 # min + 5%
|
||||
if self.__max_key in data:
|
||||
self._attr_native_max_value = int(data[self.__max_key])
|
||||
super()._updated(data)
|
||||
|
||||
|
||||
class LevelEntity(ValueUpdateEntity):
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
|
||||
|
||||
class MinBatteryLevelEntity(LevelEntity):
|
||||
_attr_icon = "mdi:battery-charging-10"
|
||||
|
||||
|
||||
class MaxBatteryLevelEntity(LevelEntity):
|
||||
_attr_icon = "mdi:battery-charging-90"
|
||||
|
||||
|
||||
class MinGenStartLevelEntity(LevelEntity):
|
||||
_attr_icon = "mdi:engine"
|
||||
|
||||
|
||||
class MaxGenStopLevelEntity(LevelEntity):
|
||||
_attr_icon = "mdi:engine-off"
|
||||
|
||||
|
||||
class SetTempEntity(ValueUpdateEntity):
|
||||
_attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
|
||||
|
||||
9
config/custom_components/ecoflow_cloud/recorder.py
Normal file
9
config/custom_components/ecoflow_cloud/recorder.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from homeassistant.core import callback, HomeAssistant
|
||||
|
||||
from custom_components.ecoflow_cloud import ATTR_STATUS_UPDATES, ATTR_STATUS_DATA_LAST_UPDATE, \
|
||||
ATTR_STATUS_LAST_UPDATE, ATTR_STATUS_PHASE
|
||||
|
||||
|
||||
@callback
|
||||
def exclude_attributes(hass: HomeAssistant) -> set[str]:
|
||||
return {ATTR_STATUS_UPDATES, ATTR_STATUS_DATA_LAST_UPDATE, ATTR_STATUS_LAST_UPDATE, ATTR_STATUS_PHASE}
|
||||
48
config/custom_components/ecoflow_cloud/select.py
Normal file
48
config/custom_components/ecoflow_cloud/select.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from typing import Callable, 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 custom_components.ecoflow_cloud import EcoflowMQTTClient, DOMAIN
|
||||
from custom_components.ecoflow_cloud.entities import BaseSelectEntity
|
||||
|
||||
|
||||
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].selects(client))
|
||||
|
||||
|
||||
class DictSelectEntity(BaseSelectEntity):
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_available = False
|
||||
|
||||
def __init__(self, client: EcoflowMQTTClient, mqtt_key: str, title: str, options: dict[str, int],
|
||||
command: Callable[[int], dict[str, any]] | None, enabled: bool = True, auto_enable: bool = False):
|
||||
super().__init__(client, mqtt_key, title, command, enabled, auto_enable)
|
||||
self.__options_dict = options
|
||||
self._attr_options = list(options.keys())
|
||||
|
||||
def options_dict(self) -> dict[str, int]:
|
||||
return self.__options_dict
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
ival = int(val)
|
||||
lval = [k for k, v in self.__options_dict.items() if v == ival]
|
||||
if len(lval) == 1:
|
||||
self._attr_current_option = lval[0]
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
async def async_select_option(self, option: str):
|
||||
if self._command:
|
||||
val = self.__options_dict[option]
|
||||
self.send_set_message(val, self.command_dict(val))
|
||||
|
||||
|
||||
class TimeoutDictSelectEntity(DictSelectEntity):
|
||||
_attr_icon = "mdi:timer-outline"
|
||||
393
config/custom_components/ecoflow_cloud/sensor.py
Normal file
393
config/custom_components/ecoflow_cloud/sensor.py
Normal file
@@ -0,0 +1,393 @@
|
||||
import math
|
||||
import logging
|
||||
from datetime import timedelta, datetime
|
||||
from typing import Any, Mapping, OrderedDict
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorEntity, BinarySensorDeviceClass
|
||||
from homeassistant.components.sensor import (SensorDeviceClass, SensorStateClass, SensorEntity)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
||||
from homeassistant.const import (PERCENTAGE,
|
||||
UnitOfElectricCurrent, UnitOfElectricPotential, UnitOfEnergy, UnitOfFrequency,
|
||||
UnitOfPower, UnitOfTemperature, UnitOfTime)
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.util import utcnow
|
||||
from homeassistant.util.dt import UTC
|
||||
|
||||
from . import DOMAIN, ATTR_STATUS_SN, ATTR_STATUS_DATA_LAST_UPDATE, ATTR_STATUS_UPDATES, \
|
||||
ATTR_STATUS_LAST_UPDATE, ATTR_STATUS_RECONNECTS, ATTR_STATUS_PHASE
|
||||
from .entities import BaseSensorEntity, EcoFlowAbstractEntity, EcoFlowDictEntity
|
||||
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].sensors(client))
|
||||
|
||||
|
||||
class MiscBinarySensorEntity(BinarySensorEntity, EcoFlowDictEntity):
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
self._attr_is_on = bool(val)
|
||||
return True
|
||||
|
||||
|
||||
class ChargingStateSensorEntity(BaseSensorEntity):
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_icon = "mdi:battery-charging"
|
||||
_attr_device_class = BinarySensorDeviceClass.BATTERY_CHARGING
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
if val == 0:
|
||||
return super()._update_value("unused")
|
||||
elif val == 1:
|
||||
return super()._update_value("charging")
|
||||
elif val == 2:
|
||||
return super()._update_value("discharging")
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class CyclesSensorEntity(BaseSensorEntity):
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_icon = "mdi:battery-heart-variant"
|
||||
_attr_state_class = SensorStateClass.TOTAL_INCREASING
|
||||
|
||||
|
||||
class FanSensorEntity(BaseSensorEntity):
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_icon = "mdi:fan"
|
||||
|
||||
|
||||
class MiscSensorEntity(BaseSensorEntity):
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
|
||||
|
||||
class LevelSensorEntity(BaseSensorEntity):
|
||||
_attr_device_class = SensorDeviceClass.BATTERY
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
|
||||
|
||||
class RemainSensorEntity(BaseSensorEntity):
|
||||
_attr_device_class = SensorDeviceClass.DURATION
|
||||
_attr_native_unit_of_measurement = UnitOfTime.MINUTES
|
||||
_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 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):
|
||||
_attr_device_class = SensorDeviceClass.TEMPERATURE
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_native_value = -1
|
||||
|
||||
|
||||
class DecicelsiusSensorEntity(TempSensorEntity):
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return super()._update_value(int(val) / 10)
|
||||
|
||||
class MilliCelsiusSensorEntity(TempSensorEntity):
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return super()._update_value(int(val) / 100)
|
||||
|
||||
class VoltSensorEntity(BaseSensorEntity):
|
||||
_attr_device_class = SensorDeviceClass.VOLTAGE
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_native_unit_of_measurement = UnitOfElectricPotential.VOLT
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_native_value = 0
|
||||
|
||||
|
||||
class MilliVoltSensorEntity(BaseSensorEntity):
|
||||
_attr_device_class = SensorDeviceClass.VOLTAGE
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_native_unit_of_measurement = UnitOfElectricPotential.MILLIVOLT
|
||||
_attr_suggested_unit_of_measurement = UnitOfElectricPotential.VOLT
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_native_value = 3
|
||||
|
||||
|
||||
class InMilliVoltSensorEntity(MilliVoltSensorEntity):
|
||||
_attr_icon = "mdi:transmission-tower-import"
|
||||
_attr_suggested_display_precision = 0
|
||||
|
||||
|
||||
class OutMilliVoltSensorEntity(MilliVoltSensorEntity):
|
||||
_attr_icon = "mdi:transmission-tower-export"
|
||||
_attr_suggested_display_precision = 0
|
||||
|
||||
|
||||
class DecivoltSensorEntity(BaseSensorEntity):
|
||||
_attr_device_class = SensorDeviceClass.VOLTAGE
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_native_unit_of_measurement = UnitOfElectricPotential.VOLT
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_native_value = 0
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return super()._update_value(int(val) / 10)
|
||||
|
||||
|
||||
class CentivoltSensorEntity(DecivoltSensorEntity):
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return super()._update_value(int(val) / 10)
|
||||
|
||||
|
||||
class AmpSensorEntity(BaseSensorEntity):
|
||||
_attr_device_class = SensorDeviceClass.CURRENT
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_native_unit_of_measurement = UnitOfElectricCurrent.MILLIAMPERE
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_native_value = 0
|
||||
|
||||
|
||||
class DeciampSensorEntity(BaseSensorEntity):
|
||||
_attr_device_class = SensorDeviceClass.CURRENT
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_native_unit_of_measurement = UnitOfElectricCurrent.AMPERE
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_native_value = 0
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return super()._update_value(int(val) / 10)
|
||||
|
||||
|
||||
class WattsSensorEntity(BaseSensorEntity):
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_device_class = SensorDeviceClass.POWER
|
||||
_attr_native_unit_of_measurement = UnitOfPower.WATT
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_native_value = 0
|
||||
|
||||
|
||||
class EnergySensorEntity(BaseSensorEntity):
|
||||
_attr_device_class = SensorDeviceClass.ENERGY
|
||||
_attr_native_unit_of_measurement = UnitOfEnergy.WATT_HOUR
|
||||
_attr_state_class = SensorStateClass.TOTAL_INCREASING
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
ival = int(val)
|
||||
if ival > 0:
|
||||
return super()._update_value(ival)
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class CapacitySensorEntity(BaseSensorEntity):
|
||||
_attr_device_class = SensorDeviceClass.CURRENT
|
||||
_attr_native_unit_of_measurement = "mAh"
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
|
||||
|
||||
class DeciwattsSensorEntity(WattsSensorEntity):
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return super()._update_value(int(val) / 10)
|
||||
|
||||
|
||||
class InWattsSensorEntity(WattsSensorEntity):
|
||||
_attr_icon = "mdi:transmission-tower-import"
|
||||
|
||||
|
||||
class InWattsSolarSensorEntity(InWattsSensorEntity):
|
||||
_attr_icon = "mdi:solar-power"
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return super()._update_value(int(val) / 10)
|
||||
|
||||
|
||||
class OutWattsSensorEntity(WattsSensorEntity):
|
||||
_attr_icon = "mdi:transmission-tower-export"
|
||||
|
||||
|
||||
class OutWattsDcSensorEntity(WattsSensorEntity):
|
||||
_attr_icon = "mdi:transmission-tower-export"
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return super()._update_value(int(val) / 10)
|
||||
|
||||
|
||||
class InVoltSensorEntity(VoltSensorEntity):
|
||||
_attr_icon = "mdi:transmission-tower-import"
|
||||
|
||||
class InVoltSolarSensorEntity(VoltSensorEntity):
|
||||
_attr_icon = "mdi:solar-power"
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return super()._update_value(int(val) / 10)
|
||||
|
||||
class OutVoltDcSensorEntity(VoltSensorEntity):
|
||||
_attr_icon = "mdi:transmission-tower-export"
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return super()._update_value(int(val) / 10)
|
||||
|
||||
class InAmpSensorEntity(AmpSensorEntity):
|
||||
_attr_icon = "mdi:transmission-tower-import"
|
||||
|
||||
class InAmpSolarSensorEntity(AmpSensorEntity):
|
||||
_attr_icon = "mdi:solar-power"
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return super()._update_value(int(val) * 10)
|
||||
|
||||
class InEnergySensorEntity(EnergySensorEntity):
|
||||
_attr_icon = "mdi:transmission-tower-import"
|
||||
|
||||
|
||||
class OutEnergySensorEntity(EnergySensorEntity):
|
||||
_attr_icon = "mdi:transmission-tower-export"
|
||||
|
||||
|
||||
class FrequencySensorEntity(BaseSensorEntity):
|
||||
_attr_device_class = SensorDeviceClass.FREQUENCY
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_native_unit_of_measurement = UnitOfFrequency.HERTZ
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
|
||||
|
||||
class DecihertzSensorEntity(FrequencySensorEntity):
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
return super()._update_value(int(val) / 10)
|
||||
|
||||
|
||||
class StatusSensorEntity(SensorEntity, EcoFlowAbstractEntity):
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
DEADLINE_PHASE = 10
|
||||
CHECK_PHASES = [2, 4, 6]
|
||||
CONNECT_PHASES = [3, 5, 7]
|
||||
|
||||
def __init__(self, client: EcoflowMQTTClient, check_interval_sec=30):
|
||||
super().__init__(client, "Status", "status")
|
||||
self._online = 0
|
||||
self.__check_interval_sec = check_interval_sec
|
||||
self._attrs = OrderedDict[str, Any]()
|
||||
self._attrs[ATTR_STATUS_SN] = client.device_sn
|
||||
self._attrs[ATTR_STATUS_DATA_LAST_UPDATE] = self._client.data.params_time()
|
||||
self._attrs[ATTR_STATUS_UPDATES] = 0
|
||||
self._attrs[ATTR_STATUS_LAST_UPDATE] = None
|
||||
self._attrs[ATTR_STATUS_RECONNECTS] = 0
|
||||
self._attrs[ATTR_STATUS_PHASE] = 0
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
await super().async_added_to_hass()
|
||||
|
||||
params_d = self._client.data.params_observable().subscribe(self.__params_update)
|
||||
self.async_on_remove(params_d.dispose)
|
||||
|
||||
self.async_on_remove(
|
||||
async_track_time_interval(self.hass, self.__check_status, timedelta(seconds=self.__check_interval_sec)))
|
||||
|
||||
self._update_status((utcnow() - self._client.data.params_time()).total_seconds())
|
||||
|
||||
def __check_status(self, now: datetime):
|
||||
data_outdated_sec = (now - self._client.data.params_time()).total_seconds()
|
||||
phase = math.ceil(data_outdated_sec / self.__check_interval_sec)
|
||||
self._attrs[ATTR_STATUS_PHASE] = phase
|
||||
time_to_reconnect = phase in self.CONNECT_PHASES
|
||||
time_to_check_status = phase in self.CHECK_PHASES
|
||||
|
||||
if self._online == 1:
|
||||
if time_to_check_status or phase >= self.DEADLINE_PHASE:
|
||||
# online and outdated - refresh status to detect if device went offline
|
||||
self._update_status(data_outdated_sec)
|
||||
elif time_to_reconnect:
|
||||
# online, updated and outdated - reconnect
|
||||
self._attrs[ATTR_STATUS_RECONNECTS] = self._attrs[ATTR_STATUS_RECONNECTS] + 1
|
||||
self._client.reconnect()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
elif not self._client.is_connected(): # validate connection even for offline device
|
||||
self._attrs[ATTR_STATUS_RECONNECTS] = self._attrs[ATTR_STATUS_RECONNECTS] + 1
|
||||
self._client.reconnect()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def __params_update(self, data: dict[str, Any]):
|
||||
self._attrs[ATTR_STATUS_DATA_LAST_UPDATE] = self._client.data.params_time()
|
||||
if self._online == 0:
|
||||
self._update_status(0)
|
||||
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def _update_status(self, data_outdated_sec):
|
||||
if data_outdated_sec > self.__check_interval_sec * self.DEADLINE_PHASE:
|
||||
self._online = 0
|
||||
self._attr_native_value = "assume_offline"
|
||||
else:
|
||||
self._online = 1
|
||||
self._attr_native_value = "assume_online"
|
||||
|
||||
self._attrs[ATTR_STATUS_LAST_UPDATE] = utcnow()
|
||||
self._attrs[ATTR_STATUS_UPDATES] = self._attrs[ATTR_STATUS_UPDATES] + 1
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> Mapping[str, Any] | None:
|
||||
return self._attrs
|
||||
|
||||
|
||||
class QuotasStatusSensorEntity(StatusSensorEntity):
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
|
||||
def __init__(self, client: EcoflowMQTTClient):
|
||||
super().__init__(client)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
|
||||
get_reply_d = self._client.data.get_reply_observable().subscribe(self.__get_reply_update)
|
||||
self.async_on_remove(get_reply_d.dispose)
|
||||
|
||||
await super().async_added_to_hass()
|
||||
|
||||
def _update_status(self, update_delta_sec):
|
||||
if self._client.is_connected():
|
||||
self._attrs[ATTR_STATUS_UPDATES] = self._attrs[ATTR_STATUS_UPDATES] + 1
|
||||
self.send_get_message({"version": "1.1", "moduleType": 0, "operateType": "latestQuotas", "params": {}})
|
||||
else:
|
||||
super()._update_status(update_delta_sec)
|
||||
|
||||
def __get_reply_update(self, data: list[dict[str, Any]]):
|
||||
d = data[0]
|
||||
if d["operateType"] == "latestQuotas":
|
||||
self._online = d["data"]["online"]
|
||||
self._attrs[ATTR_STATUS_LAST_UPDATE] = utcnow()
|
||||
|
||||
if self._online == 1:
|
||||
self._attrs[ATTR_STATUS_SN] = d["data"]["sn"]
|
||||
self._attr_native_value = "online"
|
||||
|
||||
# ?? self._client.data.update_data(d["data"]["quotaMap"])
|
||||
else:
|
||||
self._attr_native_value = "offline"
|
||||
|
||||
self.schedule_update_ha_state()
|
||||
73
config/custom_components/ecoflow_cloud/switch.py
Normal file
73
config/custom_components/ecoflow_cloud/switch.py
Normal file
@@ -0,0 +1,73 @@
|
||||
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 BaseSwitchEntity
|
||||
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].switches(client))
|
||||
|
||||
|
||||
class EnabledEntity(BaseSwitchEntity):
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
_LOGGER.debug("Updating switch " + self._attr_unique_id + " to " + str(val))
|
||||
self._attr_is_on = bool(val)
|
||||
return True
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
if self._command:
|
||||
self.send_set_message(1, self.command_dict(1))
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
if self._command:
|
||||
self.send_set_message(0, self.command_dict(0))
|
||||
|
||||
|
||||
class DisabledEntity(BaseSwitchEntity):
|
||||
|
||||
def _update_value(self, val: Any) -> bool:
|
||||
_LOGGER.debug("Updating switch " + self._attr_unique_id + " to " + str(val))
|
||||
self._attr_is_on = not bool(val)
|
||||
return True
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
if self._command:
|
||||
self.send_set_message(0, self.command_dict(0))
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
if self._command:
|
||||
self.send_set_message(1, self.command_dict(1))
|
||||
|
||||
|
||||
class BeeperEntity(DisabledEntity):
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
if self.is_on:
|
||||
return "mdi:volume-high"
|
||||
else:
|
||||
return "mdi:volume-mute"
|
||||
|
||||
class InvertedBeeperEntity(EnabledEntity):
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
if self.is_on:
|
||||
return "mdi:volume-high"
|
||||
else:
|
||||
return "mdi:volume-mute"
|
||||
26
config/custom_components/ecoflow_cloud/translations/de.json
Normal file
26
config/custom_components/ecoflow_cloud/translations/de.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"title": "EcoFlow-Cloud",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"username": "Benutzer-Email",
|
||||
"password": "Benutzer-Passwort",
|
||||
"type": "Gerätetyp",
|
||||
"name": "Gerätename",
|
||||
"device_id": "Seriennummer des Gerät"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"power_step": "Ladeleistung (Schritte für Schieberegler)",
|
||||
"refresh_period_sec": "Datenaktualisierung in Sekunden"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
config/custom_components/ecoflow_cloud/translations/en.json
Normal file
26
config/custom_components/ecoflow_cloud/translations/en.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"title": "EcoFlow-Cloud",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"username": "User email",
|
||||
"password": "User password",
|
||||
"type": "Device type",
|
||||
"name": "Device name",
|
||||
"device_id": "Device SN"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"power_step": "Charging power slider step",
|
||||
"refresh_period_sec": "Data refresh period (sec)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
config/custom_components/ecoflow_cloud/translations/fr.json
Normal file
27
config/custom_components/ecoflow_cloud/translations/fr.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"title": "EcoFlow-Cloud",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"username": "Adresse e-mail",
|
||||
"password": "Mot de passe",
|
||||
"type": "Type d'appareil",
|
||||
"name": "Nom de l'appareil",
|
||||
"device_id": "Numéro de série"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"power_step": "Pas du curseur de puissance de charge",
|
||||
"refresh_period_sec": "Durée actualisation des données (secondes)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user