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
80 changes: 79 additions & 1 deletion packages/modules/devices/qcells/qcells/bat.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
from typing import TypedDict, Any
import logging
from typing import TypedDict, Any, Optional

from modules.common.abstract_device import AbstractBat
from modules.common.component_state import BatState
Expand All @@ -9,6 +10,25 @@
from modules.common.store import get_bat_value_store
from modules.devices.qcells.qcells.config import QCellsBatSetup

log = logging.getLogger(__name__)

# Solax/QCells Mode 8 Remote Control Registers (Holding Registers)
# Speichersteuerung via "Individual Setting - Duration Mode"
# Unterstuetzte Hardware: QCells Q.VOLT HYB-G3-3P (Solax Gen4),
# Solax Gen4/Gen5/Gen6 Hybrid und AC Wechselrichter.
REMOTE_CONTROL_MODE_REG = 0xA0 # U16: 0=Disabled, 8=Individual Duration
REMOTE_CONTROL_SET_TYPE_REG = 0xA1 # U16: 1=Set
REMOTE_CONTROL_PV_LIMIT_REG = 0xA2 # U32: PV Power Limit in Watt (keine Begrenzung = 30000)
REMOTE_CONTROL_PUSH_POWER_REG = 0xA4 # S32: Battery Push Power (+Entladung, -Ladung)
REMOTE_CONTROL_DURATION_REG = 0xA6 # U16: Dauer in Sekunden
REMOTE_CONTROL_TIMEOUT_REG = 0xA7 # U16: Timeout in Sekunden

MODE_8_INDIVIDUAL_DURATION = 8
SET_TYPE_SET = 1
PV_LIMIT_NO_CURTAILMENT = 30000
REMOTE_CONTROL_DURATION = 300
REMOTE_CONTROL_TIMEOUT = 300


class KwargsDict(TypedDict):
modbus_id: int
Expand All @@ -25,6 +45,7 @@ def initialize(self) -> None:
self.client: ModbusTcpClient_ = self.kwargs['client']
self.store = get_bat_value_store(self.component_config.id)
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
self.last_mode: Optional[str] = 'Undefined'

def update(self) -> None:
power = self.client.read_input_registers(0x0016, ModbusDataType.INT_16, unit=self.__modbus_id)
Expand All @@ -42,5 +63,62 @@ def update(self) -> None:
)
self.store.set(bat_state)

def set_power_limit(self, power_limit: Optional[int]) -> None:
unit = self.__modbus_id
log.debug(f"QCells set_power_limit: power_limit={power_limit}, "
f"last_mode={self.last_mode}")

if power_limit is None:
log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter")
if self.last_mode is not None:
with self.client:
self.client.write_register(
REMOTE_CONTROL_MODE_REG, 0, data_type=ModbusDataType.UINT_16, unit=unit)
self.last_mode = None
elif power_limit == 0:
log.debug("Aktive Batteriesteuerung. Batterie wird gestoppt (kein Entladen)")
if self.last_mode != 'stop':
self._write_mode8(power_value=0, unit=unit)
self.last_mode = 'stop'
elif power_limit > 0:
power_value = int(power_limit) * -1
log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W geladen")
if self.last_mode != 'charge':
self.last_mode = 'charge'
# Solax Mode 8: negativer Push Power Wert = Ladung
self._write_mode8(power_value, unit=unit)
elif power_limit < 0:
power_value = int(power_limit) * -1
log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W entladen")
if self.last_mode != 'discharge':
self.last_mode = 'discharge'
# Solax Mode 8: positiver Push Power Wert = Entladung
self._write_mode8(power_value, unit=unit)

def _write_mode8(self, power_value: int, unit: int) -> None:
"""Schreibt die Mode 8 Remote Control Register (0xA0-0xA7)."""
with self.client:
self.client.write_register(
REMOTE_CONTROL_MODE_REG, MODE_8_INDIVIDUAL_DURATION,
data_type=ModbusDataType.UINT_16, unit=unit)
self.client.write_register(
REMOTE_CONTROL_SET_TYPE_REG, SET_TYPE_SET,
data_type=ModbusDataType.UINT_16, unit=unit)
self.client.write_register(
REMOTE_CONTROL_PV_LIMIT_REG, PV_LIMIT_NO_CURTAILMENT,
data_type=ModbusDataType.UINT_32, unit=unit)
self.client.write_register(
REMOTE_CONTROL_PUSH_POWER_REG, power_value,
data_type=ModbusDataType.INT_32, unit=unit)
self.client.write_register(
REMOTE_CONTROL_DURATION_REG, REMOTE_CONTROL_DURATION,
data_type=ModbusDataType.UINT_16, unit=unit)
self.client.write_register(
REMOTE_CONTROL_TIMEOUT_REG, REMOTE_CONTROL_TIMEOUT,
data_type=ModbusDataType.UINT_16, unit=unit)

def power_limit_controllable(self) -> bool:
return True


component_descriptor = ComponentDescriptor(configuration_factory=QCellsBatSetup)