0 THEN GVL_MODBUS.stBMSErrorReg.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; 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); // =============================== // 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); // Call string 1 _afbStrings[0]( xEnable := _xEnableString, uiStringNumber := 0, xErrorShutdown := _xErrorShutdown, xStartBalancing := _xStartBalancing, sInverterIP := GVL_CONFIG.sInverterIpString1, rPowerInverter := _arPowerString[0], xInSafetyCheckMode := _xInSafetyCheckMode, stHMIInterface:= GVL_SCADA.stHMIInterface[0], xEmergencyStopOk:= _xEmergencyStopOk, xReleaseErrors:= _xReleaseErrors AND _tonStartupDelay.Q AND _xEtherCatString1Ok, xReleaseLimitErrors:= _xReleaseLimitsErrors AND _tonStartupDelay.Q AND _xEtherCatString1Ok, xReleaseManualMode := _xReleaseManualMode, xConfirmAlarms:= _xConfirmAlarms, xAllToManualMode := _xAllComponentsToManualMode, xResetSafety := xSafetyResterTaster); IF _afbStrings[0].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.wBMSErrorActive.stBitmap.bDCSwitchS1 := _afbStrings[0].xRepairSwitchOk; GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bSafetyIntlkString1 := (NOT _afbStrings[0].xSafetyIntlksOk); // Call string 2 _afbStrings[1]( xEnable := _xEnableString, uiStringNumber := 1, xErrorShutdown := _xErrorShutdown, xStartBalancing := _xStartBalancing, sInverterIP := GVL_CONFIG.sInverterIpString2, rPowerInverter := _arPowerString[1], xInSafetyCheckMode := _xInSafetyCheckMode, stHMIInterface:= GVL_SCADA.stHMIInterface[1], xEmergencyStopOk:= _xEmergencyStopOk, xReleaseErrors:= _xReleaseErrors AND _tonStartupDelay.Q AND _xEtherCatString2Ok, xReleaseLimitErrors:= _xReleaseLimitsErrors AND _tonStartupDelay.Q AND _xEtherCatString2Ok, xReleaseManualMode := _xReleaseManualMode, xConfirmAlarms:= _xConfirmAlarms, xAllToManualMode := _xAllComponentsToManualMode, xResetSafety := xSafetyResterTaster); IF _afbStrings[1].xError THEN _xErrorActive := TRUE; END_IF // Handle string 1 modbus error and warning GVL_MODBUS.stBMSErrorReg.wStringErrorActive.1 := _afbStrings[1].xError; GVL_MODBUS.stBMSErrorReg.wStringWarningActive.1 := _afbStrings[1].xWarning; GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.stBitmap.bDCSwitchS2 := _afbStrings[1].xRepairSwitchOk; GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bSafetyIntlkString2 := (NOT _afbStrings[1].xSafetyIntlksOk); // Copy general error to modbus error register GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.stBitmap.bError := _xErrorActive; // =============================== // 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; FOR _ui := 0 TO (GVL_CONFIG.uiNumberOfStrings-1) DO // 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 // Get auto mode status IF NOT _afbStrings[_ui].xAllModulesInAutoMode THEN _xStringsInAutoMode := FALSE; END_IF END_FOR // =============================== // 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=> ); _fbModbusRead( sIPAddr:= '127.0.0.1', nTCPPort:= 502, nUnitID:= 16#FF, nQuantity:= 48, nMBAddr:= 11000, cbLength:= SIZEOF(_ModbusDebugTest), pDestAddr:= ADR(_ModbusDebugTest), bExecute:= xDebugTest, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> , cbRead=> ); // =============================== // Copy data to modbus registers // =============================== // 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: _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := FALSE; _rAutoPowerRequest := DINT_TO_REAL(GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower); IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_AUTO(); E_BMS_CONTROL_MODE.AUTO_LOCAL: _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := 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: _xAllComponentsToManualMode := TRUE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := TRUE; 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: _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := TRUE; _xReleaseManualMode := 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: _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := 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: _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := 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: _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := 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(); END_CASE GVL_SCADA.xCanChangeControlMode := _xCanChangeMode; GVL_SCADA.eCurrentControlMode := _eBMSControlMode; // Calculate current battery dc power GVL_SCADA.diCurrentBatteryPower := REAL_TO_DINT(_afbStrings[0].stInverterData.rActDCPower + _afbStrings[1].stInverterData.rActDCPower); // 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 // Call safety fb _fbSafety(); // 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 GVL_MODBUS.stBMSErrorReg.wBMSErrorActive.wRegister := 0; GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.wRegister := 0; _xConfirmAlarms := FALSE; END_IF _fbPowerMeter24V();]]> DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) AND (NOT _xStringsErrorActive) AND _xStringsAllInAutomaticMode THEN _iState := 5; _xCanChangeMode := FALSE; _xErrorShutdown := FALSE; END_IF 5: // Check if power command is within limits IF _rAutoPowerRequest < DINT_TO_REAL(GVL_CONFIG.diMaxStringDischargePower) AND _rAutoPowerRequest > DINT_TO_REAL(GVL_CONFIG.diMaxStringChargingPower) 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); _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); _xEnableString := FALSE; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _xCanChangeMode := TRUE; _iState := 45; END_IF IF (ABS(_rAutoPowerRequest) < DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) 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;//DINT_TO_REAL(GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower); // Check if the battery should still be active IF (_rAutoPowerRequest = 0.0) 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; _iState := 35; END_IF // Shutdown triggered by battery fully charged IF GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus = E_CHARGE_STATUS.CHARGING AND ((_rMaxCurrentInverterDCVoltage >= GVL_CONFIG.rStringFullyChargedVoltage) OR _rHighestSegmentVoltage >= GVL_CONFIG.rMaximumUnitVoltage) 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); // 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; _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; _iState := 35; END_IF END_IF // Check for errors IF _xStringsErrorActive THEN _xEnableString := FALSE; _xErrorShutdown := TRUE; _tonBeginShutdown(In := FALSE); GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _xCanChangeMode := TRUE; _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 _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 THEN _rPowerInverter := GVL_CONFIG.rAbsShutdownDischargePower; ELSE _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 _iState := 5; END_IF 45: // Wait for shutdown of string to be done 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)) AND (NOT _xStringsErrorActive) AND _xStringsAllInAutomaticMode THEN _xCanChangeMode := FALSE; _iState := 5; END_IF // Check for errors IF _xStringsErrorActive THEN _xErrorShutdown := TRUE; _iState := 1000; END_IF 1000: // Error state GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0; _xEnableString := FALSE; _rPowerInverter := 0.0; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.ERROR; _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 // Calculate string power balancing IF _rStringsSumVoltage <> 0 THEN FOR _ui := 0 TO (GVL_CONFIG.uiNumberOfStrings-1) DO // Discharging IF _rPowerInverter > 0 THEN _arPowerString[_ui] := _rPowerInverter * (_afbStrings[_ui].rCurrentVoltage / _rStringsSumVoltage); // Charging ELSIF _rPowerInverter < 0 THEN _arPowerString[_ui] := _rPowerInverter * (1.0 - (_afbStrings[_ui].rCurrentVoltage / _rStringsSumVoltage)); // Nothing ELSE _arPowerString[_ui] := 0.0; END_IF END_FOR ELSE FOR _ui := 0 TO (GVL_CONFIG.uiNumberOfStrings-1) DO _arPowerString[_ui] := 0.0; END_FOR END_IF ]]>