0 THEN GVL_MODBUS.wConfirmAlarms := 0; _xConfirmAlarms := TRUE; END_IF // Ack alarms from hardware button _fbRTrigHardwareAck(CLK := _xHarwareResetButton); IF _fbRTrigHardwareAck.Q THEN _xConfirmAlarms := TRUE; END_IF // =============================== // EtherCAT communication error // =============================== _wEtherCATState := UINT_TO_WORD(_uiEtherCATState); _xEtherCatString1Ok := (_wEtherCATState AND 16#8000) = 0; _wDebug1 := (_wEtherCATState AND 16#8000); _xEtherCatString2Ok := (_wEtherCATState AND 16#2000) = 0; _wDebug2 := (_wEtherCATState AND 16#2000); // String 1 on X1 = Port D = 0x8000 IF (NOT _xEtherCatString1Ok) AND (NOT _fbEtherCATErrorString1.bRaised) THEN _fbEtherCATErrorString1.Raise(0); GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.stBitmap.bEthercat := 1; END_IF IF _fbEtherCATErrorString1.bRaised AND (_xEtherCatString1Ok) THEN _fbEtherCATErrorString1.Clear(0, FALSE); END_IF IF _fbEtherCATErrorString1.eConfirmationState = TcEventConfirmationState.WaitForConfirmation AND _xConfirmAlarms THEN _fbEtherCATErrorString1.Confirm(0); END_IF // String 2 on X2 = Port B = 0x2000 IF (NOT _xEtherCatString2Ok) AND (NOT _fbEtherCATErrorString2.bRaised) THEN _fbEtherCATErrorString2.Raise(0); GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.stBitmap.bEthercat := 1; END_IF IF _fbEtherCATErrorString2.bRaised AND _xEtherCatString2Ok THEN _fbEtherCATErrorString2.Clear(0, FALSE); END_IF IF _fbEtherCATErrorString2.eConfirmationState = TcEventConfirmationState.WaitForConfirmation AND _xConfirmAlarms THEN _fbEtherCATErrorString2.Confirm(0); END_IF // =============================== // Safety // =============================== xSafetyErrAck := xSafetyResterTaster; _xShowAckEmergencyStop := (NOT _xEmergencyStopOk) OR _xStringSafetyComError; IF (NOT _xEmergencyStopOk) AND (NOT _fbEStopNotOk.bRaised) THEN _fbEStopNotOk.Raise(0); END_IF IF _xEmergencyStopOk AND _fbEStopNotOk.bRaised THEN _fbEStopNotOk.Clear(0, FALSE); END_IF IF _fbEStopNotOk.eConfirmationState = TcEventConfirmationState.WaitForConfirmation AND _xConfirmAlarms THEN _fbEStopNotOk.Confirm(0); END_IF GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.stBitmap.bEStop := (NOT _xEmergencyStopOk) OR _fbEStopNotOk.bRaised OR (_fbEStopNotOk.eConfirmationState <> TcEventConfirmationState.Confirmed); // =============================== // handle NA Protection // =============================== IF _xConfirmAlarms THEN _xNAProtectionTripped := FALSE; END_IF IF NOT xNAProtectionOK THEN _xNAProtectionTripped := TRUE; IF NOT _fbNAProtectionAlarm.bRaised THEN _fbNAProtectionAlarm.Raise(); END_IF END_IF IF _xNAProtectionTripped THEN _xErrorActive := TRUE; END_IF IF xNAProtectionOK AND _fbNAProtectionAlarm.bRaised THEN _fbNAProtectionAlarm.Clear(0, FALSE); END_IF IF _fbNAProtectionAlarm.eConfirmationState = TcEventConfirmationState.WaitForConfirmation AND _xConfirmAlarms THEN _fbNAProtectionAlarm.Confirm(0); END_IF GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.stBitmap.bNAProtectionTripped := _xNAProtectionTripped; // =============================== // Hardware reset button part 1 // =============================== _tonHardwareResetButton(IN := _xHarwareResetButton); _rtHardwareResetButton(CLK := _tonHardwareResetButton.Q); // _xConfirmAlarms := TRUE; // =============================== // Handle Manual mode release // =============================== //IF _iState = 0 THEN // _xReleaseManualMode := TRUE; //ELSE // _xReleaseManualMode := FALSE; //END_IF // =============================== // Handle UPS events // =============================== _fbUPS( sNetID:= '', iPLCPort:= 851, tTimeout:= DEFAULT_ADS_TIMEOUT, eUpsMode:= eSUPS_WrPersistData_Shutdown, ePersistentMode:= SPDM_2PASS, tRecoverTime:= T#10S, bPowerFailDetect=> , eState=> ); IF _xFirstCycle THEN _xFirstCycle := FALSE; _afbStrings[0].Name := 'String 1'; _afbStrings[1].Name := 'String 2'; END_IF // Dely release of errors during PLC startup phase _tonStartupDelay(IN := TRUE); // =============================== // Temperature sensor control cabinet BMS // =============================== _fbTempCabinetBMS( stScalingConfig:= GVL_CONFIG.stConfigCabinetTemp, stEWConfig:= GVL_CONFIG.stEWLCabinetTemp, stEWDelayConfig:= GVL_CONFIG.stEWDCabinetTemp, xReleaseErrors:= _xReleaseErrors, xReleaseLimitErrors:= _xReleaseLimitsErrors, xReleaseHardwareErrors:= _xReleaseErrors, xConfirmAlarms:= _xConfirmAlarms, stHMIInterface=> GVL_SCADA.stTempCabinetBMS); // Set modbus error register bit IF _fbTempCabinetBMS.xWarningHigh THEN GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bTCabinetBMSHigh := TRUE; END_IF IF _fbTempCabinetBMS.xWarning THEN _xWarningActive := TRUE; END_IF IF _fbTempCabinetBMS.xError THEN _xErrorActive := TRUE; END_IF // Call string 1 _afbStrings[0]( xEnable := _xEnableString AND GVL_CONFIG.axStringEnabled[0] AND xNAProtectionOK, xReleaseInverterPower := _xReleaseInverterPower, uiStringNumber := 0, eOperationMode := _eStringOpMode, xErrorShutdown := _xErrorShutdown, xStartBalancing := _xStartBalancing, sInverterIP := GVL_CONFIG.sInverterIpString1, timInverterStartupTimeout := GVL_CONFIG.timInverterStartupTimeout, rPowerInverter := _arPowerString[0], xInSafetyCheckMode := _xInSafetyCheckMode, stHMIInterface:= GVL_SCADA.stHMIInterface[0], xEmergencyStopOk:= _xEmergencyStopOk, xReleaseErrors:= _xReleaseErrors AND _tonStartupDelay.Q AND _xEtherCatString1Ok AND GVL_CONFIG.axStringEnabled[0], xReleaseLimitErrors:= _xReleaseLimitsErrors AND _tonStartupDelay.Q AND _xEtherCatString1Ok, xReleaseManualMode := _xReleaseManualMode, xConfirmAlarms:= _xConfirmAlarms, xAllToManualMode := _xAllComponentsToManualMode, xResetSafety := xSafetyResterTaster, refuStringErrorsModbus := GVL_MODBUS.stBMSErrorReg.wString1ErrorActive); IF _afbStrings[0].xWarning THEN _xWarningActive := TRUE; END_IF IF _afbStrings[0].xError THEN _xErrorActive := TRUE; END_IF // =============================== // Temperature sensor control cabinet SCS string 1 // =============================== _fbTempCabinetSCSString1( stScalingConfig:= GVL_CONFIG.stConfigCabinetTemp, stEWConfig:= GVL_CONFIG.stEWLCabinetTemp, stEWDelayConfig:= GVL_CONFIG.stEWDCabinetTemp, xReleaseErrors:= _xReleaseErrors, xReleaseLimitErrors:= _xReleaseLimitsErrors, xReleaseHardwareErrors:= _xReleaseErrors, xConfirmAlarms:= _xConfirmAlarms, stHMIInterface=> GVL_SCADA.stHMIInterface[0].stTempCabinetSCS); IF _fbTempCabinetSCSString1.xWarning THEN _xWarningActive := TRUE; END_IF IF _fbTempCabinetSCSString1.xError THEN _xErrorActive := TRUE; END_IF // Handle string 1 modbus error and warning GVL_MODBUS.stBMSErrorReg.wStringErrorActive.0 := _afbStrings[0].xError; GVL_MODBUS.stBMSErrorReg.wStringWarningActive.0 := _afbStrings[0].xWarning; GVL_MODBUS.stBMSErrorReg.wString1ErrorActive.stBitmap.bDCSwitch := (NOT _afbStrings[0].xRepairSwitchOk); GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bSafetyIntlkString1 := (NOT _afbStrings[0].xSafetyIntlksOk); GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bTCabinetString1Module1High := _afbStrings[0].xTempCabinetModule1Warning; GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bTCabinetString1Module2High := _afbStrings[0].xTempCabinetModule2Warning; GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bTCabinetString1Module3High := _afbStrings[0].xTempCabinetModule3Warning; GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bTCabinetSCSString1High := _fbTempCabinetSCSString1.xWarningHigh; // Copy inverter data to Modbus registers GVL_MODBUS.stModbusEMSComm.stModbusReg11.stInverterData.eInverter1CurrentState := _afbStrings[0].stInverterInfos.eCurrentState; GVL_MODBUS.stModbusEMSComm.stModbusReg11.stInverterData.eInverter1RequestedState := _afbStrings[0].stInverterInfos.eRequestedState; GVL_MODBUS.stModbusEMSComm.stModbusReg11.stInverterData.eInverter1PCUState := _afbStrings[0].stInverterInfos.ePCUState; GVL_MODBUS.stModbusEMSComm.stModbusReg11.stInverterData.eInverter1PCUError := _afbStrings[0].stInverterInfos.ePCUError; // Call string 2 _afbStrings[1]( xEnable := _xEnableString AND GVL_CONFIG.axStringEnabled[1] AND xNAProtectionOK, xReleaseInverterPower := _xReleaseInverterPower, uiStringNumber := 1, eOperationMode := _eStringOpMode, xErrorShutdown := _xErrorShutdown, xStartBalancing := _xStartBalancing, sInverterIP := GVL_CONFIG.sInverterIpString2, timInverterStartupTimeout := GVL_CONFIG.timInverterStartupTimeout, rPowerInverter := _arPowerString[1], xInSafetyCheckMode := _xInSafetyCheckMode, stHMIInterface:= GVL_SCADA.stHMIInterface[1], xEmergencyStopOk:= _xEmergencyStopOk, xReleaseErrors:= _xReleaseErrors AND _tonStartupDelay.Q AND _xEtherCatString2Ok AND GVL_CONFIG.axStringEnabled[1], xReleaseLimitErrors:= _xReleaseLimitsErrors AND _tonStartupDelay.Q AND _xEtherCatString2Ok, xReleaseManualMode := _xReleaseManualMode, xConfirmAlarms:= _xConfirmAlarms, xAllToManualMode := _xAllComponentsToManualMode, xResetSafety := xSafetyResterTaster, refuStringErrorsModbus := GVL_MODBUS.stBMSErrorReg.wString2ErrorActive); IF _afbStrings[1].xWarning THEN _xWarningActive := TRUE; END_IF IF _afbStrings[1].xError THEN _xErrorActive := TRUE; END_IF // =============================== // Temperature sensor control cabinet SCS string 2 // =============================== _fbTempCabinetSCSString2( stScalingConfig:= GVL_CONFIG.stConfigCabinetTemp, stEWConfig:= GVL_CONFIG.stEWLCabinetTemp, stEWDelayConfig:= GVL_CONFIG.stEWDCabinetTemp, xReleaseErrors:= _xReleaseErrors, xReleaseLimitErrors:= _xReleaseLimitsErrors, xReleaseHardwareErrors:= _xReleaseErrors, xConfirmAlarms:= _xConfirmAlarms, stHMIInterface=> GVL_SCADA.stHMIInterface[1].stTempCabinetSCS); IF _fbTempCabinetSCSString2.xWarning THEN _xWarningActive := TRUE; END_IF IF _fbTempCabinetSCSString2.xError THEN _xErrorActive := TRUE; END_IF // Handle string 2 modbus error and warning GVL_MODBUS.stBMSErrorReg.wStringErrorActive.1 := _afbStrings[1].xError; GVL_MODBUS.stBMSErrorReg.wStringWarningActive.1 := _afbStrings[1].xWarning; GVL_MODBUS.stBMSErrorReg.wString2ErrorActive.stBitmap.bDCSwitch := (NOT _afbStrings[1].xRepairSwitchOk); GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bSafetyIntlkString2 := (NOT _afbStrings[1].xSafetyIntlksOk); GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bTCabinetString2Module1High := _afbStrings[1].xTempCabinetModule1Warning; GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bTCabinetString2Module2High := _afbStrings[1].xTempCabinetModule2Warning; GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bTCabinetString2Module3High := _afbStrings[1].xTempCabinetModule3Warning; GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bTCabinetSCSString2High := _fbTempCabinetSCSString2.xWarningHigh; // Copy inverter data to Modbus registers GVL_MODBUS.stModbusEMSComm.stModbusReg11.stInverterData.eInverter2CurrentState := _afbStrings[1].stInverterInfos.eCurrentState; GVL_MODBUS.stModbusEMSComm.stModbusReg11.stInverterData.eInverter2RequestedState := _afbStrings[1].stInverterInfos.eRequestedState; GVL_MODBUS.stModbusEMSComm.stModbusReg11.stInverterData.eInverter2PCUState := _afbStrings[1].stInverterInfos.ePCUState; GVL_MODBUS.stModbusEMSComm.stModbusReg11.stInverterData.eInverter2PCUError := _afbStrings[1].stInverterInfos.ePCUError; // =============================== // Get global string status information // =============================== _xStringsReady := TRUE; _xStringsErrorActive := FALSE; _xStringsInSchutdownDischargeMode := FALSE; _xStringsShutdownDischargeAllowed := TRUE; _xStringsAllInAutomaticMode := TRUE; _xStringsOff := TRUE; _xStringsBalancingDone := TRUE; _rMaxCurrentInverterDCVoltage := 0.0; _rMinCurrentInverterDCVoltage := 10_000; _rHighestSegmentVoltage := 0.0; _rSmallestSegmentVoltage := 1_000.0; _xStringsInAutoMode := TRUE; _uiNumberOfActiveStrings := 0; _rStringsSumVoltage := 0; _xStringSafetyComError := FALSE; FOR _ui := 0 TO (GVL_CONFIG.uiNumberOfStrings-1) DO // Check for safety com error IF _afbStrings[_ui].xSafetyComError THEN _xStringSafetyComError := TRUE; END_IF // Ignore deactivated strings IF (NOT GVL_CONFIG.axStringEnabled[_ui]) THEN CONTINUE; END_IF // Count number of active strings _uiNumberOfActiveStrings := _uiNumberOfActiveStrings + 1; // Check ready state IF (NOT _afbStrings[_ui].xReady) THEN _xStringsReady := FALSE; END_IF // Check error state IF _afbStrings[_ui].xError THEN _xStringsErrorActive := TRUE; END_IF // Check for shutdown discharge mode IF _afbStrings[_ui].xInShutdownDischargeMode THEN _xStringsInSchutdownDischargeMode := TRUE; END_IF // Check for shutdown discharge allowed IF (NOT _afbStrings[_ui].xShutdownDischargeAllowed) THEN _xStringsShutdownDischargeAllowed := FALSE; END_IF // Check for all in automatic mode IF (NOT _afbStrings[_ui].xAllModulesInAutoMode) THEN _xStringsAllInAutomaticMode := FALSE; END_IF // Check for all strings off IF (NOT _afbStrings[_ui].xOff) THEN _xStringsOff := FALSE; END_IF // check balancing done IF (NOT _afbStrings[_ui].xBalancingDone) THEN _xStringsBalancingDone := FALSE; END_IF // Check for max dc voltage IF _rMaxCurrentInverterDCVoltage < _afbStrings[_ui].stInverterData.rActDCVoltage THEN _rMaxCurrentInverterDCVoltage := _afbStrings[_ui].stInverterData.rActDCVoltage; END_IF // Check for min DC voltage IF _rMinCurrentInverterDCVoltage > _afbStrings[_ui].stInverterData.rActDCVoltage THEN _rMinCurrentInverterDCVoltage := _afbStrings[_ui].stInverterData.rActDCVoltage; END_IF // Calculate highest segment voltage IF _rSmallestSegmentVoltage > _afbStrings[_ui].rSmallestSegmentVoltage THEN _rSmallestSegmentVoltage := _afbStrings[_ui].rSmallestSegmentVoltage; END_IF // Calculate lowest segment voltage IF _rHighestSegmentVoltage < _afbStrings[_ui].rHighestSegmentVoltage THEN _rHighestSegmentVoltage := _afbStrings[_ui].rHighestSegmentVoltage; END_IF // Calculate sum voltage _rStringsSumVoltage := _rStringsSumVoltage + _afbStrings[_ui].rCurrentVoltage; // Get auto mode status IF NOT _afbStrings[_ui].xAllModulesInAutoMode THEN _xStringsInAutoMode := FALSE; END_IF END_FOR // =============================== // Calculate max and min power depending on number of active strings // =============================== _rMinPower := _uiNumberOfActiveStrings * DINT_TO_REAL(GVL_CONFIG.diMaxStringChargingPower); _rMaxPower := _uiNumberOfActiveStrings * DINT_TO_REAL(GVL_CONFIG.diMaxStringDischargePower); // =============================== // Calculate sum power for string balancing // =============================== // _rStringsSumVoltage := _afbStrings[0].rCurrentVoltage + _afbStrings[1].rCurrentVoltage; // =============================== // Hardware reset button part 2 // =============================== _xShowErrorOnButton := _xErrorActive; // HMI Feedback (* GVL_SCADA.stHMIInterface[0].rVoltage := _afbStrings[0].rCurrentVoltage; IF _afbStrings[0].eStatus = E_COMPONENT_STATUS.ON THEN IF _iState = 30 AND _rPowerInverter > 0 THEN GVL_SCADA.stHMIInterface[0].eStatus := E_COMPONENT_STATUS.DISCHARGING; ELSIF _iState = 30 AND _rPowerInverter < 0 THEN GVL_SCADA.stHMIInterface[0].eStatus := E_COMPONENT_STATUS.CHARGING; ELSE GVL_SCADA.stHMIInterface[0].eStatus :=_afbStrings[0].eStatus; END_IF ELSE GVL_SCADA.stHMIInterface[0].eStatus :=_afbStrings[0].eStatus; END_IF *) // =============================== // Read modbus request count // =============================== _timADSReadTimer(IN := NOT _fbADSReader.BUSY, PT := T#200MS); _fbADSReader( NETID:= '', PORT:= 10500, IDXGRP:= 16#2000, IDXOFFS:= 1, LEN:= 4, DESTADDR:= ADR(GVL_MODBUS.stModbusEMSComm.stModbusReg11.udiLifeMessage), READ:= _timADSReadTimer.Q, TMOUT:= T#1S, BUSY=> , ERR=> , ERRID=> ); // Check for change in life count messages // If changed, we have a valid communication with the EMS IF ABS(GVL_MODBUS.stModbusEMSComm.stModbusReg11.udiLifeMessage - _udiLastEMSLifeMessage) > 0 THEN _xNoEMSLifeMessageChange := FALSE; ELSE _xNoEMSLifeMessageChange := TRUE; END_IF _udiLastEMSLifeMessage := GVL_MODBUS.stModbusEMSComm.stModbusReg11.udiLifeMessage; // Create Heartbeat timeout signal with delay _fbEMSHeartbeatTimeout( xSignal:= _xNoEMSLifeMessageChange, xRelease:= _xReleaseEMSHeartbeatError, timOnDelay:= GVL_CONFIG.timEMSHeartbeatTimeout, timOffDelay:= T#0S, xReleaseSignal=> _xEMSHeartbeatNotOK); // EMS Heartbeat timeout error message handling IF _xEMSHeartbeatNotOK AND (NOT _fbEMSHeartbeatAlarm.bRaised) THEN _fbEMSHeartbeatAlarm.Raise(0); END_IF IF (NOT _xEMSHeartbeatNotOK) AND _fbEMSHeartbeatAlarm.bRaised THEN _fbEMSHeartbeatAlarm.Clear(0, FALSE); END_IF IF _xConfirmAlarms AND _fbEMSHeartbeatAlarm.eConfirmationState = TcEventConfirmationState.WaitForConfirmation THEN _fbEMSHeartbeatAlarm.Confirm(0); END_IF // Ledge heartbeatNOK _rtrigEMSHeartbeakNotOK(CLK := _xEMSHeartbeatNotOK); IF _rtrigEMSHeartbeakNotOK.Q THEN _xEMSHeartbeatNotOKLedge := TRUE; END_IF IF NOT _xEMSHeartbeatNotOK AND _xConfirmAlarms THEN _xEMSHeartbeatNotOKLedge := FALSE; END_IF // =============================== // Copy data to modbus registers // =============================== // heartbeat GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.stBitmap.bEMSHeartbeatError := _xEMSHeartbeatNotOKLedge; // Modbus current inverter values GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentActivePower := 0; GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentReactivePower := 0; GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase1 := 0; GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase2 := 0; GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase3 := 0; FOR _ui := 0 TO (GVL_CONFIG.uiNumberOfStrings-1) DO GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentActivePower := GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentActivePower + REAL_TO_DINT(_afbStrings[_ui].stInverterData.rActACPower); GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentReactivePower := GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentReactivePower + REAL_TO_DINT(_afbStrings[_ui].stInverterData.rActReactivePower); GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase1 := GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase1 + REAL_TO_DINT(_afbStrings[_ui].stInverterData.rActtACPhaseACurrent); GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase2 := GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase2 + REAL_TO_DINT(_afbStrings[_ui].stInverterData.rActtACPhaseBCurrent); GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase3 := GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase3 + REAL_TO_DINT(_afbStrings[_ui].stInverterData.rActtACPhaseCCurrent); END_FOR // Set Modbus mirror values GVL_MODBUS.stModbusEMSComm.stModbusReg11.diSetpointActivePowerMirror := GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower; GVL_MODBUS.stModbusEMSComm.stModbusReg11.rSetpointCosPhiMirror := GVL_MODBUS.stModbusEMSComm.stModbusReg12.rSetpointCosPhi; // =============================== // State machine // =============================== CASE _eBMSControlMode OF E_BMS_CONTROL_MODE.AUTO_REMOTE: _eStringOpMode := E_STRING_OPERATING_MODE.AUTOMATIC; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower; _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := FALSE; _xReleaseEMSHeartbeatError := TRUE; // Only set power to EMS requested power if the EMS heartbeat is ok // Otherwise shutdown battery IF (NOT _xEMSHeartbeatNotOKLedge) AND _iState <> 1010 THEN _rAutoPowerRequest := DINT_TO_REAL(GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower); ELSIF _iState = 1010 THEN _rAutoPowerRequest := 0; ELSE _rAutoPowerRequest := 0; _xErrorShutdown := TRUE; _iState := 1000; END_IF IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_AUTO(); E_BMS_CONTROL_MODE.AUTO_LOCAL: _eStringOpMode := E_STRING_OPERATING_MODE.AUTOMATIC; _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := FALSE; _xReleaseEMSHeartbeatError := FALSE; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.TESTING; _rAutoPowerRequest := DINT_TO_REAL(GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic); IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_AUTO(); E_BMS_CONTROL_MODE.MANUAL: _eStringOpMode := E_STRING_OPERATING_MODE.AUTOMATIC; _xAllComponentsToManualMode := TRUE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := TRUE; _xReleaseEMSHeartbeatError := FALSE; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.MAINTENANCE; IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_MANUAL(); E_BMS_CONTROL_MODE.SAFETY_CHECK: _eStringOpMode := E_STRING_OPERATING_MODE.SAFETY_CHECK; _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := TRUE; _xReleaseManualMode := FALSE; _xReleaseEMSHeartbeatError := FALSE; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.MAINTENANCE; IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_SAFETY_CHECK(); E_BMS_CONTROL_MODE.CAPACITY_TEST: _eStringOpMode := E_STRING_OPERATING_MODE.AUTOMATIC; _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := FALSE; _xReleaseEMSHeartbeatError := FALSE; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.TESTING; IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_CAPACITY_TEST(); E_BMS_CONTROL_MODE.BALANCING: _eStringOpMode := E_STRING_OPERATING_MODE.BALANCING; _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := FALSE; _xReleaseEMSHeartbeatError := FALSE; IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _xStartBalancing := FALSE; _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_BALANCING(); E_BMS_CONTROL_MODE.CYCLING: _eStringOpMode := E_STRING_OPERATING_MODE.AUTOMATIC; _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := FALSE; _xReleaseEMSHeartbeatError := FALSE; //IF (_iState <> 30) OR GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic = 0 THEN _rAutoPowerRequest := DINT_TO_REAL(GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic); //END_IF IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_AUTO(); GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := REAL_TO_DINT(_rAutoPowerRequest); E_BMS_CONTROL_MODE.PRECHARGE: _eStringOpMode := E_STRING_OPERATING_MODE.PRECHARGE; _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := FALSE; _xReleaseEMSHeartbeatError := FALSE; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.MAINTENANCE; IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_PRECHARGE(); E_BMS_CONTROL_MODE.DH: _eStringOpMode := E_STRING_OPERATING_MODE.AUTOMATIC; _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := FALSE; _xReleaseEMSHeartbeatError := FALSE; // Goto error state if a string has an error IF _xStringsErrorActive THEN _xDHActive := FALSE; _iStateDH := 1000; END_IF CASE _iStateDH OF 0: // Idle, wait for command _rPowerDH := DINT_TO_REAL(GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic); IF (ABS(_rPowerDH) > 100.0) THEN _xDHActive := TRUE; _iStateDH := 5; END_IF 5: // Wait for pre startup IF _xBatteryActive THEN _rAutoPowerRequest := _rPowerDH; _fbTONDHCycleTime(IN := TRUE); _iStateDH := 10; END_IF 10: // First power cycle _fbTONDHCycleTime(IN := TRUE); IF _fbTONDHCycleTime.Q THEN _rAutoPowerRequest := 0; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _fbTONDHCycleTime(IN := FALSE); _iStateDH := 20; END_IF 20: // First pause _fbTONDHCycleTime(IN := TRUE); IF _fbTONDHCycleTime.Q THEN _rAutoPowerRequest := _rPowerDH * -1; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := REAL_TO_DINT(_rAutoPowerRequest); _fbTONDHCycleTime(IN := FALSE); _iStateDH := 30; END_IF 30: // Second power phase _fbTONDHCycleTime(IN := TRUE); IF _fbTONDHCycleTime.Q THEN _rAutoPowerRequest := 0; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _fbTONDHCycleTime(IN := FALSE); _xDHActive := FALSE; _iStateDH := 0; END_IF 1000: // Error state _rAutoPowerRequest := 0; _rPowerDH := 0; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _fbTONDHCycleTime(IN := FALSE); _iStateDH := 1010; 1010: // Wait for reset IF _xConfirmAlarms THEN _iStateDH := 0; END_IF END_CASE IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_AUTO(); GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := REAL_TO_DINT(_rAutoPowerRequest); END_CASE GVL_SCADA.xCanChangeControlMode := _xCanChangeMode; GVL_SCADA.eCurrentControlMode := _eBMSControlMode; // Calculate current battery dc power GVL_SCADA.diCurrentBatteryPower := REAL_TO_DINT(_afbStrings[0].stInverterData.rActACPower + _afbStrings[1].stInverterData.rActACPower); // Read power values if commanded _fbPowerMeterPower( xGetEnergyCounters:= _xGetPowerMeterData, sIpAddress:= '192.168.42.75', lrEnergyFromGrid=> GVL_SCADA.lrChargedPowerValueWH, lrEnergyIntoGrid=> GVL_SCADA.lrDischargedPowerValueWH, xBusy=> , xError=> ); _fbPowerMeter24V( xGetEnergyCounters:= _xGetPowerMeterData, sIpAddress:= '192.168.42.80', lrEnergyFromGrid=> GVL_SCADA.lrLastCycleUtilityPowerValueWh, lrEnergyIntoGrid=> , xBusy=> , xError=> ); IF _xGetPowerMeterData THEN _xGetPowerMeterData := FALSE; END_IF // Handle modbus global warning and error flag GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.stBitmap.bError := _xErrorActive OR _xErrorBMSSMActive; // Call safety fb _fbSafety(refuStringErrorsModbus := GVL_MODBUS.stBMSErrorReg.wBMSErrorActive); // Check if all modules are in auto mode IF _xStringsInAutoMode AND _fbNoAutomaticModeAlarm.bRaised THEN _fbNoAutomaticModeAlarm.Clear(0, TRUE); END_IF // Copy auto mode warning message to modbus register GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bNotAllInAuto := (NOT _xStringsInAutoMode); // Reset automatic buttons IF GVL_SCADA.stAutomaticModeHMI.stStartAutoButton.xRequest THEN GVL_SCADA.stAutomaticModeHMI.stStartAutoButton.xRequest := FALSE; END_IF IF GVL_SCADA.stAutomaticModeHMI.stStopAutoButton.xRequest THEN GVL_SCADA.stAutomaticModeHMI.stStopAutoButton.xRequest := FALSE; END_IF // Reset alarm confirmation IF _xConfirmAlarms OR _rtHardwareResetButton.Q THEN _xErrorBMSSMActive := FALSE; GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.wRegister := 0; GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.wRegister := 0; GVL_MODBUS.stBMSErrorReg.wString1ErrorActive.wRegister := 0; GVL_MODBUS.stBMSErrorReg.wString2ErrorActive.wRegister := 0; _xConfirmAlarms := FALSE; END_IF _fbPowerMeter24V(); _fbTowerLight( xAutoInStop := NOT _xBatteryActive, xWarningActive := _xWarningActive, xWarningConfirmPending := FALSE, xErrorActive := _xErrorActive OR _xErrorBMSSMActive, xErrorConfirmPending := FALSE);]]> DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) OR GVL_MODBUS.stModbusEMSComm.stModbusReg12.wBMSControlsRegister.stBitmap.bScheduleActive OR _xDHActive) AND (NOT _xStringsErrorActive) AND _xStringsAllInAutomaticMode AND (NOT _xEMSHeartbeatNotOK) THEN _iState := 5; _xReleaseInverterPower := FALSE; _xCanChangeMode := FALSE; _xErrorShutdown := FALSE; END_IF 5: // Check if power command is within limits IF _rAutoPowerRequest <= _rMaxPower AND _rAutoPowerRequest >= _rMinPower THEN _xEnableString := TRUE; _xStartBalancing := FALSE; _iState := 10; ELSE // Set error bitmap flag GVL_MODBUS.stModbusEMSComm.stModbusReg11.lwErrorBitmap.0 := 1; // Goto error state _iState := 1000; END_IF 10: // Wait for string to be ready _fbStringReadyTimeout(IN := TRUE, PT := GVL_CONFIG.timStringReadyTimeout); IF _xStringsReady AND (NOT _xStringsErrorActive) THEN _xGetPowerMeterData := TRUE; _fbStringReadyTimeout(IN := FALSE); GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.stBitmap.bStringsNotReadyInTime := 1; _rPowerInverter := 0.0; // Set active parallel members (modbus) GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := GVL_CONFIG.uiNumberOfStrings; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.ACTIVE; _iState := 30; END_IF IF _xStringsErrorActive OR _fbStringReadyTimeout.Q THEN _fbStringReadyTimeout(IN := FALSE); _xStringNotReadyInTime := _fbStringReadyTimeout.Q; _xEnableString := FALSE; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _xCanChangeMode := TRUE; _iState := 1000; END_IF IF ((ABS(_rAutoPowerRequest) < DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) OR _xEMSHeartbeatNotOK) AND (NOT GVL_MODBUS.stModbusEMSComm.stModbusReg12.wBMSControlsRegister.stBitmap.bScheduleActive) AND (NOT _xDHActive) THEN _fbStringReadyTimeout(IN := FALSE); _xEnableString := FALSE; _xCanChangeMode := TRUE; _iState := 45; END_IF 30: // String and inverter enabled // Set inverter power to modbus requested power _rPowerInverter := _rAutoPowerRequest; _xReleaseInverterPower := TRUE; _xBatteryActive := TRUE; // Check if the battery should still be active IF (_rAutoPowerRequest = 0.0) AND (NOT GVL_MODBUS.stModbusEMSComm.stModbusReg12.wBMSControlsRegister.stBitmap.bScheduleActive) AND (NOT _xDHActive) THEN _xNoPowerRequested := TRUE; ELSE _xNoPowerRequested := FALSE; END_IF // Set battery status IF _rAutoPowerRequest > 0 THEN GVL_SCADA.eCycleStatus := E_CYCLE_STATUS.DISCHARGE_STARTED; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.DISCHARGING; ELSIF _rAutoPowerRequest < 0 THEN GVL_SCADA.eCycleStatus := E_CYCLE_STATUS.CHARGE_STARTED; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.CHARGING; ELSE GVL_SCADA.eCycleStatus := E_CYCLE_STATUS.OFF; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.UNDEFINED; END_IF // Add small delay before shutdown by EMS is detected _tonBeginShutdown(IN := _xNoPowerRequested); // shutdown triggered from EMS IF _tonBeginShutdown.Q THEN _tonBeginShutdown(In := FALSE); IF _rPowerInverter < 0 THEN GVL_SCADA.eCycleStatus := E_CYCLE_STATUS.CHARGE_ENDED; ELSIF _rPowerInverter > 0 THEN GVL_SCADA.eCycleStatus := E_CYCLE_STATUS.DISCHARGE_ENDED; END_IF // Set inverter to zero power _rPowerInverter := 0.0; // Start string shutdown _xEnableString := FALSE; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.DISCHARGING; GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0; _xCVChargingLatched := FALSE; _fbPIControl.Reset(); _iState := 35; END_IF // Charge with constant Voltage at charging end IF GVL_CONFIG.xCVCharging AND (_rAutoPowerRequest < 0.0) AND (_rHighestSegmentVoltage >= (GVL_CONFIG.rMaximumUnitVoltage)) THEN _xCVChargingLatched := TRUE; END_IF IF _xCVChargingLatched THEN // Reglerfreigabe _rCVSetpoint := GVL_CONFIG.rMaximumUnitVoltage; ELSE // Nachführbetrieb _rCVSetpoint := _rHighestSegmentVoltage; END_IF _fbPT1CVAntiAliasFilter(rInput := _rHighestSegmentVoltage, timT := T#200MS); _fbPIControl( xEnable := _xCVChargingLatched, rSP:= _rCVSetpoint, rPV:= _fbPT1CVAntiAliasFilter.rOutput, rKp:= GVL_CONFIG.rCVKp, rTn:= GVL_CONFIG.rCVTn, xSaturated:= _fbLimit.xClamped); _rPowerInverter := _rAutoPowerRequest - _fbPIControl.rMV; _fbLimit( rIn := _rPowerInverter, rMin := _rMinPower, rMax := _rMaxPower, rOut => _rPowerInverter); // Delatch if charging power requested is lower than cv power IF _xCVChargingLatched AND (_rAutoPowerRequest > _rPowerInverter) THEN _xCVChargingLatched := FALSE; END_IF _xFullByVoltage := (NOT GVL_CONFIG.xCVCharging) AND (_rPowerInverter < 0) AND ((_rMaxCurrentInverterDCVoltage >= GVL_CONFIG.rStringFullyChargedVoltage) OR (_rHighestSegmentVoltage >= GVL_CONFIG.rMaximumUnitVoltage)); _xCVCurrentLimitReached := ((GVL_SCADA.stHMIInterface[0].stInverterData.rActDCCurrent > GVL_CONFIG.rMinCVCurrentForFull) AND GVL_CONFIG.axStringEnabled[0]) OR ((GVL_SCADA.stHMIInterface[1].stInverterData.rActDCCurrent > GVL_CONFIG.rMinCVCurrentForFull) AND GVL_CONFIG.axStringEnabled[1]); _fbTONCVLow( IN := (_rPowerInverter < 0) AND _xCVCurrentLimitReached AND _xCVChargingLatched, PT := GVL_CONFIG.timCVCurrentReached ); _xFullByCV := GVL_CONFIG.xCVCharging AND _fbTONCVLow.Q; // Shutdown triggered by battery fully charged IF GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus = E_CHARGE_STATUS.CHARGING AND (_xFullByVoltage OR _xFullByCV) THEN _xGetPowerMeterData := TRUE; IF (_eBMSControlMode = E_BMS_CONTROL_MODE.CYCLING) THEN GVL_SCADA.eCycleStatus := E_CYCLE_STATUS.DISCHARGE_STARTED; _rAutoPowerRequest := _rAutoPowerRequest * -1; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := REAL_TO_DINT(_rAutoPowerRequest); ELSE _tonBeginShutdown(In := FALSE); _fbTONCVLow(In := FALSE); // Send message _fbBatteryFullMessage.Send(0); // Set inverter to zero power _rPowerInverter := 0.0; // Set local remote to zero power GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; // Start string shutdown _xEnableString := FALSE; // Change battery status GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.FULL; GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0; _xCVChargingLatched := FALSE; _fbPIControl.Reset(); _iState := 35; END_IF END_IF // Shutdown triggered by battery empty IF GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus = E_CHARGE_STATUS.DISCHARGING AND ((_rMinCurrentInverterDCVoltage <= GVL_CONFIG.rStringEmptyVoltage) OR _rSmallestSegmentVoltage <= GVL_CONFIG.rMinimumUnitVoltage) THEN _xGetPowerMeterData := TRUE; IF (_eBMSControlMode = E_BMS_CONTROL_MODE.CYCLING) THEN GVL_SCADA.eCycleStatus := E_CYCLE_STATUS.CHARGE_STARTED; _rAutoPowerRequest := _rAutoPowerRequest * -1; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := REAL_TO_DINT(_rAutoPowerRequest); ELSE _tonBeginShutdown(In := FALSE); // Send Message _fbBatteryEmptyMessage.Send(0); // Set local remote to zero power GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; // Set inverter to zero power _rPowerInverter := 0.0; // Start string shutdown _xEnableString := FALSE; // Change battery status GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.EMPTY; GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0; _xCVChargingLatched := FALSE; _fbPIControl.Reset(); _iState := 35; END_IF END_IF // Check for errors IF _xStringsErrorActive OR _xEMSHeartbeatNotOK THEN _xReleaseInverterPower := FALSE; _xEnableString := FALSE; _xErrorShutdown := TRUE; _tonBeginShutdown(In := FALSE); GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _xCanChangeMode := TRUE; _xCVChargingLatched := FALSE; _fbPIControl.Reset(); _iState := 45; END_IF 35: // Wait for string to be in shutdown discharge mode IF _xStringsInSchutdownDischargeMode THEN // Check if we are allowed to discharge during shutdown with inverter IF GVL_CONFIG.xShutdownDischargeWithInverter THEN _iState := 40; ELSE _xBatteryActive := FALSE; _xReleaseInverterPower := FALSE; _rPowerInverter := 0.0; _xEnableString := FALSE; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _xCanChangeMode := TRUE; GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.OFF; _iState := 45; END_IF END_IF // Check for errors IF _xStringsErrorActive THEN _xErrorShutdown := TRUE; _iState := 1000; END_IF 40: // Wait for inverter discharge done IF _xStringsShutdownDischargeAllowed OR _xEMSHeartbeatNotOK THEN _rPowerInverter := GVL_CONFIG.rAbsShutdownDischargePower; ELSE _xBatteryActive := FALSE; _xReleaseInverterPower := FALSE; _xGetPowerMeterData := TRUE; GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0; _rPowerInverter := 0.0; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _xEnableString := FALSE; _xCanChangeMode := TRUE; _iState := 45; END_IF // Check for errors IF _xStringsErrorActive THEN _xErrorShutdown := TRUE; _iState := 1000; END_IF // Restart if possible IF (ABS(_rAutoPowerRequest) > DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) AND (NOT _xStringsErrorActive) AND _xStringsAllInAutomaticMode THEN IF _rAutoPowerRequest < 0 AND GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus <> E_CHARGE_STATUS.FULL THEN _iState := 0; END_IF IF _rAutoPowerRequest > 0 AND GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus <> E_CHARGE_STATUS.EMPTY THEN _iState := 0; END_IF END_IF 45: // Wait for shutdown of string to be done _xCanChangeMode := TRUE; IF (NOT _xStringsInSchutdownDischargeMode) AND _xStringsOff THEN GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.OFF; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.UNDEFINED; GVL_SCADA.eCycleStatus := E_CYCLE_STATUS.OFF; _iState := 0; END_IF // Restart if possible IF ((ABS(_rAutoPowerRequest) > DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) OR GVL_MODBUS.stModbusEMSComm.stModbusReg12.wBMSControlsRegister.stBitmap.bScheduleActive OR _xDHActive) AND (NOT _xStringsErrorActive) AND _xStringsAllInAutomaticMode THEN _xCanChangeMode := FALSE; IF (_rAutoPowerRequest < 0 OR GVL_MODBUS.stModbusEMSComm.stModbusReg12.wBMSControlsRegister.stBitmap.bScheduleActive) AND GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus <> E_CHARGE_STATUS.FULL THEN _iState := 0; END_IF IF (_rAutoPowerRequest > 0 OR GVL_MODBUS.stModbusEMSComm.stModbusReg12.wBMSControlsRegister.stBitmap.bScheduleActive) AND GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus <> E_CHARGE_STATUS.EMPTY THEN _iState := 0; END_IF IF _xDHActive THEN _iState := 0; END_IF IF (_rAutoPowerRequest <> 0 OR GVL_MODBUS.stModbusEMSComm.stModbusReg12.wBMSControlsRegister.stBitmap.bScheduleActive) THEN IF (_rAutoPowerRequest <= 0 AND (GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus <> E_CHARGE_STATUS.FULL)) OR (_rAutoPowerRequest >= 0) AND (GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus <> E_CHARGE_STATUS.EMPTY) THEN _iState := 0; END_IF END_IF END_IF // Check for errors IF _xStringsErrorActive THEN _xErrorShutdown := TRUE; _iState := 1000; END_IF 1000: // Error state GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0; _xReleaseInverterPower := FALSE; _xEnableString := FALSE; _xBatteryActive := FALSE; _rPowerInverter := 0.0; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.ERROR; _xErrorBMSSMActive := TRUE; _xStringNotReadyInTime := FALSE; _iState := 1010; 1010: // Wait for reset from error state IF (_rAutoPowerRequest = 0.0) AND (NOT _xStringsErrorActive) AND _xConfirmAlarms THEN // Reset modbus error register GVL_MODBUS.stModbusEMSComm.stModbusReg11.lwErrorBitmap := 0; // Reset modbus error flag GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.OFF; // Reset error shutdown flag _xErrorShutdown := FALSE; // Goto init state _iState := 0; _xCanChangeMode := TRUE; END_IF END_CASE // String not ready in time alarm _fbStringReadyTimeoutAlarm(xActive := _xStringNotReadyInTime , xAcknowledge := _xConfirmAlarms); IF GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.stBitmap.bStringsNotReadyInTime AND _fbStringReadyTimeoutAlarm.ClearedAndConfirmed THEN GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.stBitmap.bStringsNotReadyInTime := 0; END_IF // Battery already full warning IF (_rAutoPowerRequest < 0) AND GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus = E_CHARGE_STATUS.FULL THEN GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bBatteryAlreadyFull := 1; _fbBatteryAlreadyFullWarning(xActive := TRUE); _xWarningActive := TRUE; ELSE GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bBatteryAlreadyFull := 0; _fbBatteryAlreadyFullWarning(xActive := FALSE); END_IF // Battery already empty IF (_rAutoPowerRequest > 0) AND GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus = E_CHARGE_STATUS.EMPTY THEN GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bBatteryAlreadyEmpty := 1; _fbBatteryAlreadyEmptyWarning(xActive := TRUE); _xWarningActive := TRUE; ELSE GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bBatteryAlreadyEmpty := 0; _fbBatteryAlreadyEmptyWarning(xActive := FALSE); END_IF // Calculate string power balancing IF _rStringsSumVoltage <> 0 AND (_uiNumberOfActiveStrings <> 0) THEN FOR _ui := 0 TO (GVL_CONFIG.uiNumberOfStrings-1) DO IF GVL_CONFIG.axStringEnabled[_ui] THEN // Calculate delta u to middle voltage _rDeltaUm := (_afbStrings[_ui].rCurrentVoltage * _uiNumberOfActiveStrings - _rStringsSumVoltage) / _rStringsSumVoltage; // Discharging IF _rPowerInverter > 0 THEN _arPowerString[_ui] := (_rPowerInverter / _uiNumberOfActiveStrings) * ( 1 + (_rDeltaUm * GVL_CONFIG.rBalancingFactor)); // Charging ELSIF _rPowerInverter < 0 THEN _arPowerString[_ui] := (_rPowerInverter / _uiNumberOfActiveStrings) * ( 1 - (_rDeltaUm * GVL_CONFIG.rBalancingFactor)); // Nothing ELSE _arPowerString[_ui] := 0.0; END_IF END_IF END_FOR ELSE FOR _ui := 0 TO (GVL_CONFIG.uiNumberOfStrings-1) DO _arPowerString[_ui] := 0.0; END_FOR END_IF ]]>