Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 77 additions & 54 deletions packages/modules/devices/solaredge/solaredge/bat.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
import pymodbus


from control import data


from modules.common import modbus
from modules.common.abstract_device import AbstractBat
from modules.common.component_state import BatState
Expand All @@ -24,11 +21,12 @@
log = logging.getLogger(__name__)

FLOAT32_UNSUPPORTED = -0xffffff00000000000000000000000000
MAX_DISCHARGE_LIMIT = 5000
DEFAULT_CONTROL_MODE = 1 # Control Mode Max Eigenverbrauch
REMOTE_CONTROL_MODE = 4 # Control Mode Remotesteuerung
DEFAULT_COMMAND_MODE = 0 # Command Mode ohne Steuerung
ACTIVE_COMMAND_MODE = 7 # Command Mode Max Eigenverbrauch bei Steuerung
MAX_CHARGEDISCHARGE_LIMIT = 5000
CONTROL_MODE_MSC = 1 # Storage Control Mode Maximize Self Consumption
CONTROL_MODE_REMOTE = 4 # Control Mode Remotesteuerung
REMOTE_CONTROL_COMMAND_MODE_DEFAULT = 0 # Default RC Command Mode ohne Steuerung
REMOTE_CONTROL_COMMAND_MODE_CHARGE = 3 # RC Command Mode Charge from PV+AC
REMOTE_CONTROL_COMMAND_MODE_MSC = 7 # RC Command Mode Maximize Self Consumtion


class KwargsDict(TypedDict):
Expand All @@ -45,9 +43,10 @@ class SolaredgeBat(AbstractBat):
"Battery2InstantaneousPower": (0xe274, ModbusDataType.FLOAT_32,),
"StorageControlMode": (0xe004, ModbusDataType.UINT_16,),
"StorageBackupReserved": (0xe008, ModbusDataType.FLOAT_32,),
"StorageChargeDischargeDefaultMode": (0xe00a, ModbusDataType.UINT_16,),
"RemoteControlCommandModeDefault": (0xe00a, ModbusDataType.UINT_16,),
"RemoteControlCommandMode": (0xe00d, ModbusDataType.UINT_16,),
"RemoteControlCommandDischargeLimit": (0xe010, ModbusDataType.FLOAT_32,),
"RemoteControlChargeLimit": (0xe00e, ModbusDataType.FLOAT_32,),
"RemoteControlDischargeLimit": (0xe010, ModbusDataType.FLOAT_32,),
}

def __init__(self, component_config: SolaredgeBatSetup, **kwargs: Any) -> None:
Expand All @@ -61,7 +60,7 @@ def initialize(self) -> None:
self.store = get_bat_value_store(self.component_config.id)
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
self.min_soc = 13
self.StorageControlMode_Read = DEFAULT_CONTROL_MODE
self.StorageControlMode_Read = CONTROL_MODE_MSC # Default Control Mode Set to MSC if not Read
self.last_mode = 'undefined'

def update(self) -> None:
Expand Down Expand Up @@ -124,47 +123,33 @@ def set_power_limit(self, power_limit: Optional[int]) -> None:
# Use 1 as fallback if battery_index is not set
battery_index = getattr(self.component_config.configuration, "battery_index", 1)

try:
power_limit_mode = data.data.bat_all_data.data.config.power_limit_mode
except AttributeError:
log.warning("power_limit_mode not found, assuming 'no_limit'")
power_limit_mode = 'no_limit'

if power_limit_mode == 'no_limit' and self.last_mode != 'limited':
"""
Keine Speichersteuerung, andere Steuerungen zulassen (SolarEdge One, ioBroker, Node-Red etc.).
Falls andere Steuerungen vorhanden sind, sollten diese nicht beeinflusst werden,
daher erfolgt im Modus "Immer" der Speichersteuerung keine Steuerung.
"""
return

if power_limit is None:
# Keine Ladung mit Speichersteuerung.
if self.last_mode == 'limited':
# Steuerung deaktivieren.
if power_limit is None: # No Bat Control should be used.
if self.last_mode in ('discharge-mode', 'charge-mode'):
# Disable Bat Control
log.debug(f"Speicher{battery_index}:Keine Steuerung gefordert, Steuerung deaktivieren.")
values_to_write = {
"RemoteControlCommandDischargeLimit": MAX_DISCHARGE_LIMIT,
"StorageChargeDischargeDefaultMode": DEFAULT_COMMAND_MODE,
"RemoteControlCommandMode": DEFAULT_COMMAND_MODE,
"RemoteControlChargeLimit": MAX_CHARGEDISCHARGE_LIMIT,
"RemoteControlDischargeLimit": MAX_CHARGEDISCHARGE_LIMIT,
"RemoteControlCommandModeDefault": REMOTE_CONTROL_COMMAND_MODE_DEFAULT,
"RemoteControlCommandMode": REMOTE_CONTROL_COMMAND_MODE_DEFAULT,
"StorageControlMode": self.StorageControlMode_Read,
}
self._write_registers(values_to_write, unit)
self.last_mode = None
else:
return

elif abs(power_limit) >= 0:
elif power_limit <= 0: # Limit Discharge Mode should be used.
"""
Ladung mit Speichersteuerung.
SolarEdge entlaedt den Speicher immer nur bis zur SoC-Reserve.
Steuerung beenden, wenn der SoC vom Speicher die SoC-Reserve unterschreitet.
SolarEdge discharges the battery only to SoC-Reserve.
Disable Remote Control if SoC of battery is lower than SoC-Reserve.
"""
registers_to_read = [
f"Battery{battery_index}StateOfEnergy",
"StorageControlMode",
"StorageBackupReserved",
"RemoteControlCommandDischargeLimit",
"RemoteControlCommandMode",
"RemoteControlDischargeLimit",
]
try:
values = self._read_registers(registers_to_read, unit)
Expand All @@ -177,44 +162,82 @@ def set_power_limit(self, power_limit: Optional[int]) -> None:
log.warning(f"Speicher{battery_index}: Invalid SoC: {soc}")
soc_reserve = max(int(self.min_soc + 2), int(values["StorageBackupReserved"]))
log.debug(f"SoC-Reserve Speicher{battery_index}: {int(soc_reserve)}%.")
discharge_limit = int(values["RemoteControlCommandDischargeLimit"])
discharge_limit = int(values["RemoteControlDischargeLimit"])

if values["StorageControlMode"] == REMOTE_CONTROL_MODE: # Speichersteuerung ist aktiv.
if (values["StorageControlMode"] == CONTROL_MODE_REMOTE and
values["RemoteControlCommandMode"] == REMOTE_CONTROL_COMMAND_MODE_MSC):
# RC Discharge Mode active.
if soc_reserve > soc:
# Speichersteuerung erst deaktivieren, wenn SoC-Reserve unterschritten wird.
# Darf wegen 2 Speichern nicht bereits bei SoC-Reserve deaktiviert werden!
# Disable Remote Control if SOC is lower than SOC-RESERVE.
# toDo: Problem with 2 batteries is unsolved.
log.debug(f"Speicher{battery_index}: Steuerung deaktivieren. SoC-Reserve unterschritten")
values_to_write = {
"RemoteControlCommandDischargeLimit": MAX_DISCHARGE_LIMIT,
"StorageChargeDischargeDefaultMode": DEFAULT_COMMAND_MODE,
"RemoteControlCommandMode": DEFAULT_COMMAND_MODE,
"RemoteControlDischargeLimit": MAX_CHARGEDISCHARGE_LIMIT,
"RemoteControlCommandModeDefault": REMOTE_CONTROL_COMMAND_MODE_DEFAULT,
"RemoteControlCommandMode": REMOTE_CONTROL_COMMAND_MODE_DEFAULT,
"StorageControlMode": self.StorageControlMode_Read,
}
self._write_registers(values_to_write, unit)
self.last_mode = None

elif discharge_limit not in range(int(abs(power_limit)) - 10, int(abs(power_limit)) + 10):
# Limit nur bei Abweichung von mehr als 10W, um Konflikte bei 2 Speichern zu verhindern.
# Limit only if difference is more than 10W, needed with more than 1 battery.
log.debug(f"Discharge-Limit Speicher{battery_index}: {int(abs(power_limit))}W.")
values_to_write = {
"RemoteControlCommandDischargeLimit": int(min(abs(power_limit), MAX_DISCHARGE_LIMIT))
"RemoteControlDischargeLimit": int(min(abs(power_limit), MAX_CHARGEDISCHARGE_LIMIT))
}
self._write_registers(values_to_write, unit)
self.last_mode = 'limited'
self.last_mode = 'discharge-mode'

else: # Speichersteuerung ist inaktiv.
else: # Remote Control not active.
if soc_reserve < soc:
# Speichersteuerung nur aktivieren, wenn SoC ueber SoC-Reserve.
# Enable Remote Control if SoC above SoC-Reserve.
log.debug(f"Discharge-Limit aktivieren, Speicher{battery_index}: {int(abs(power_limit))}W.")
self.StorageControlMode_Read = values["StorageControlMode"]
values_to_write = {
"StorageControlMode": REMOTE_CONTROL_MODE,
"StorageChargeDischargeDefaultMode": ACTIVE_COMMAND_MODE,
"RemoteControlCommandMode": ACTIVE_COMMAND_MODE,
"RemoteControlCommandDischargeLimit": int(min(abs(power_limit), MAX_DISCHARGE_LIMIT))
"StorageControlMode": CONTROL_MODE_REMOTE,
"RemoteControlCommandModeDefault": REMOTE_CONTROL_COMMAND_MODE_MSC,
"RemoteControlCommandMode": REMOTE_CONTROL_COMMAND_MODE_MSC,
"RemoteControlDischargeLimit": int(min(abs(power_limit), MAX_CHARGEDISCHARGE_LIMIT))
}
self._write_registers(values_to_write, unit)
self.last_mode = 'limited'
self.last_mode = 'discharge-mode'

elif power_limit > 0: # Charge Mode should be used
registers_to_read = [
"StorageControlMode",
"RemoteControlCommandMode",
"RemoteControlChargeLimit",
]
try:
values = self._read_registers(registers_to_read, unit)
except pymodbus.exceptions.ModbusException as e:
log.error(f"Failed to read registers: {e}")
self.fault_state.error(f"Modbus read error: {e}")
return

if (values["StorageControlMode"] == CONTROL_MODE_REMOTE and
values["RemoteControlCommandMode"] == REMOTE_CONTROL_COMMAND_MODE_CHARGE):
# Remote Control Charge Mode active.
log.debug(
f"Ladung Speicher.{battery_index}: {int(min(abs(power_limit), MAX_CHARGEDISCHARGE_LIMIT))}W.")
values_to_write = {
"RemoteControlChargeLimit": int(min(abs(power_limit), MAX_CHARGEDISCHARGE_LIMIT))
}
self._write_registers(values_to_write, unit)
self.last_mode = 'charge-mode'

else: # Remote Control Charge Mode inactive.
log.debug(f"Aktivierung Laden Speicher{battery_index}: {int(abs(power_limit))}W.")
self.StorageControlMode_Read = values["StorageControlMode"]
values_to_write = {
"StorageControlMode": CONTROL_MODE_REMOTE,
"RemoteControlCommandModeDefault": REMOTE_CONTROL_COMMAND_MODE_CHARGE,
"RemoteControlCommandMode": REMOTE_CONTROL_COMMAND_MODE_CHARGE,
"RemoteControlChargeLimit": int(min(abs(power_limit), MAX_CHARGEDISCHARGE_LIMIT))
}
self._write_registers(values_to_write, unit)
self.last_mode = 'charge-mode'

def _read_registers(self, register_names: list, unit: int) -> Dict[str, Union[int, float]]:
values = {}
Expand Down