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) 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); // =============================== // 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 AND GVL_CONFIG.axStringEnabled[0], uiStringNumber := 0, eOperationMode := _eStringOpMode, 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 AND GVL_CONFIG.axStringEnabled[0], 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 := (NOT _afbStrings[0].xRepairSwitchOk); GVL_MODBUS.stBMSErrorReg.wBMSWarningActive.stBitmap.bSafetyIntlkString1 := (NOT _afbStrings[0].xSafetyIntlksOk); // Call string 2 _afbStrings[1]( xEnable := _xEnableString AND GVL_CONFIG.axStringEnabled[1], uiStringNumber := 1, eOperationMode := _eStringOpMode, 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 AND GVL_CONFIG.axStringEnabled[1], 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 := (NOT _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; _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 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 // =============================== // 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: _eStringOpMode := E_STRING_OPERATING_MODE.AUTOMATIC; _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 _xEMSHeartbeatNotOK) THEN _rAutoPowerRequest := DINT_TO_REAL(GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower); ELSE _rAutoPowerRequest := 0; 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 _iStateDH := 1000; END_IF CASE _iStateDH OF 0: // Idle, wait for command _rAutoPowerRequest := DINT_TO_REAL(GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic); IF (ABS(_rAutoPowerRequest) > 100.0) THEN _rPowerDH := _rAutoPowerRequest; _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; 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); _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 // 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 AND (NOT _xEMSHeartbeatNotOK) 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)) OR _xEMSHeartbeatNotOK 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; // 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 OR _xEMSHeartbeatNotOK 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 OR _xEMSHeartbeatNotOK 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 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 ]]>