Files
Uniper_PLC/PLC/POUs/MAIN.TcPOU
Markus.Neukirch f0e6143997 IBN changes
added sync units for cabinet temperature, changes in modbus interface to EMS (1.0.4 and 1.0.5), added error counter to modbus communication, lot of changes to kaco (faults, consecutive errors, bms error messages), isolation error ledge, allowed startbalancing when on shutdown, tower light integration
2025-09-05 14:24:37 +02:00

1535 lines
49 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4026.12">
<POU Name="MAIN" Id="{bbd7302c-91ce-4697-9f4b-743f57ca5819}" SpecialFunc="None">
<Declaration><![CDATA[PROGRAM MAIN
VAR
_xEmergencyStopOk AT %I* : BOOL;
_xShowAckEmergencyStop AT %Q* : BOOL;
xNAProtectionOK AT %I* : BOOL;
_xNAProtectionTripped : BOOL := FALSE;
_xReleaseErrors : BOOL := TRUE;
_xReleaseLimitsErrors : BOOL := TRUE;
_xConfirmAlarms : BOOL;
_xEnableString : BOOL;
_xReleaseInverterPower : BOOL;
_xStartBalancing : BOOL;
_xCanChangeMode : BOOL := TRUE;
{attribute 'OPC.UA.DA' := '0'}
_afbStrings : ARRAY[0..1] OF FB_String[('String 1'), ('String 2')];
// Battery shutdown due to error
_xErrorShutdown : BOOL := FALSE;
// State machine state
_iState : INT;
_iStateSafetyCheck : INT;
_iStateBalancing : INT;
_iStatePrecharge : INT;
_iStateDH : INT;
// Start safety check mode
_xStartSafetyCheck : BOOL;
// Start precharge mode
_xStartPrecharge : BOOL;
// Auto remote and auto local power request
_rAutoPowerRequest : REAL;
// Internal inverter power
_rPowerInverter : REAL;
// Flag for zero power indication
_xNoPowerRequested : BOOL;
// Startup delay for error release during plc startup
_tonStartupDelay : TON := (PT := T#10S);
// Small delay for inverter shutdown
_tonBeginShutdown : TON := (PT := T#10S);
// Not all strings in automatic mode
_fbNoAutomaticModeAlarm : FB_TcAlarm;
// Emergency stop not ok alarm
_fbEStopNotOk : FB_TcAlarm;
// String 1 Error Mssage
_fbEtherCATErrorString1 : FB_TcAlarm;
_stECString1ErrSI : FB_TcSourceInfo;
// String 2 Error Mssage
_fbEtherCATErrorString2 : FB_TcAlarm;
_stECString2ErrSI : FB_TcSourceInfo;
// EMS heartbeat alarm
_fbEMSHeartbeatAlarm : FB_TcAlarm;
// NA Protection Alarm
_fbNAProtectionAlarm : FB_TcAlarm;
// First cycle tag
_xFirstCycle : BOOL := TRUE;
// ADS reader for modbus server data
_fbADSReader : ADSREAD;
// Timer for ADS read
_timADSReadTimer : TON;
// Old EMS lifecount message
_udiLastEMSLifeMessage : UDINT;
// No change in life counter from EMS detected
_xNoEMSLifeMessageChange : BOOL;
// EMS heartbeat not ok signal
_xEMSHeartbeatNotOK : BOOL;
_xEMSHeartbeatNotOKLedge : BOOL;
_rtrigEMSHeartbeakNotOK : R_TRIG;
// Error signal for no EMS Heartbeat
_fbEMSHeartbeatTimeout : FB_ReleaseSignal;
// Release EMS heartbeat timeout signal
_xReleaseEMSHeartbeatError : BOOL;
// Release manual mode
_xReleaseManualMode : BOOL;
// Current BMS control mode (Auto local, Auto remote, etc...)
// On restart star in manual mode (so the ems can not directly start the bms)
_eBMSControlMode : E_BMS_CONTROL_MODE := E_BMS_CONTROL_MODE.AUTO_LOCAL;
// UPS
_fbUPS : FB_S_UPS_BAPI;
// Safety
{attribute 'analysis' := '-33'}
xSafetyRun AT %Q* : BOOL := TRUE;
xSafetyErrAck AT %Q* : BOOL;
xSafetyResterTaster AT %I* : BOOL;
// Hardware reset button
_xHarwareResetButton AT %I* : BOOL;
_xShowErrorOnButton AT %Q* : BOOL;
_tonHardwareResetButton : TON := (PT := T#1S);
_rtHardwareResetButton : R_TRIG;
_xErrorActive : BOOL;
_xWarningActive : BOOL;
// Battery in safety check mode
_xInSafetyCheckMode : BOOL;
// Battery full message
_fbBatteryFullMessage : FB_TcMessage;
_fbBatteryEmptyMessage : FB_TcMessage;
// Smallest segment voltage
_rSmallestSegmentVoltage : REAL;
// Highest segment voltage
_rHighestSegmentVoltage : REAL;
// Safety
_fbSafety : FB_Safety;
// String EtherCAT state
_uiEtherCATState AT%I* : UINT;
_wEtherCATState : WORD;
_xEtherCatString1Ok : BOOL;
_xEtherCatString2Ok : BOOL;
// Flag to set all components in manual mode
_xAllComponentsToManualMode : BOOL;
// Hardware reset button rising edge trigger
_fbRTrigHardwareAck : R_TRIG;
_fbStringReadyTimeout : TON;
// Sum of voltage of all active strings
_rStringsSumVoltage : REAL;
_rDeltaUm : REAL;
_arPowerString : ARRAY[0..(GVL_CONFIG.uiNumberOfStrings-1)] OF REAL;
// Temperature sensor SCS String 1
_fbTempCabinetSCSString1 : FB_AnalogInput('String 1 - SCS - T1_Cabinet');
// Temperature sensor SCS String 1
_fbTempCabinetSCSString2 : FB_AnalogInput('String 2 - SCS - T1_Cabinet');
// Temperature sensor BMS cabinet
_fbTempCabinetBMS : FB_AnalogInput('BMS - T1_Cabinet');
_ui : UINT := 0;
_xStringsReady : BOOL;
_xStringsErrorActive : BOOL;
_xStringsInSchutdownDischargeMode : BOOL;
_xStringsShutdownDischargeAllowed : BOOL;
_xStringsAllInAutomaticMode : BOOL;
_xStringsOff : BOOL;
_xStringsBalancingDone : BOOL;
_xStringsInAutoMode : BOOL;
_xStringSafetyComError : BOOL;
_eStringOpMode : E_STRING_OPERATING_MODE;
_rMaxCurrentInverterDCVoltage : REAL;
_rMinCurrentInverterDCVoltage : REAL;
_wDebug1 : WORD;
_wDebug2 : WORD;
_fbPowerMeterPower : FB_PowerMeter;
_fbPowerMeter24V : FB_PowerMeter;
// Number of activated strings (from configuration)
_uiNumberOfActiveStrings : UINT;
_xGetPowerMeterData : BOOL;
_rPowerDH : REAL;
_fbTONDHCycleTime : TON := (PT := T#15M);
// tower light
_fbTowerLight : FB_TowerLight;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[IF _xFirstCycle THEN
_xFirstCycle := FALSE;
_xGetPowerMeterData := TRUE;
_fbBatteryFullMessage.CreateEx(stEventEntry := TC_EVENTS.BMSEvents.BatteryFull, 0);
_fbBatteryEmptyMessage.CreateEx(stEventEntry := TC_EVENTS.BMSEvents.BatteryEmpty, 0);
_fbEStopNotOk.CreateEx(stEventEntry := TC_EVENTS.BMSEvents.EmergencyStopNotOk, TRUE, 0);
_stECString1ErrSI.Clear();
_stECString1ErrSI.nId := 1;
_stECString1ErrSI.sName := 'MAIN';
_fbEtherCATErrorString1.CreateEx(stEventEntry := TC_EVENTS.BMSEvents.EthercatStringError, TRUE, _stECString1ErrSI);
_fbEtherCATErrorString1.ipArguments.Clear().AddString('1');
_stECString2ErrSI.Clear();
_stECString2ErrSI.nId := 2;
_stECString2ErrSI.sName := 'MAIN';
_fbEtherCATErrorString2.CreateEx(stEventEntry := TC_EVENTS.BMSEvents.EthercatStringError, TRUE, _stECString2ErrSI);
_fbEtherCATErrorString2.ipArguments.Clear().AddString('2');
_afbStrings[0].Name := 'String 1';
_afbStrings[1].Name := 'String 2';
_fbEMSHeartbeatAlarm.CreateEx(stEventEntry := TC_EVENTS.BMSEvents.EMSHeartbeatTimeout, TRUE, 0);
_fbNAProtectionAlarm.CreateEx(stEventEntry := TC_EVENTS.BMSEvents.NAProtectionTripped, TRUE, 0);
END_IF
// Reset error flag
_xErrorActive := FALSE;
// Reset warning flag
_xWarningActive := FALSE;
// Ack alarms from HMI
_xConfirmAlarms := GVL_SCADA.stAckAlarmsButton.xRequest;
IF GVL_SCADA.stAckAlarmsButton.xRequest THEN
GVL_SCADA.stAckAlarmsButton.xRequest := FALSE;
END_IF
// Ack alarms through modbus
IF GVL_MODBUS.wConfirmAlarms > 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,
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;
// 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,
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 1 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 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
// 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;
_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
_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(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
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 := FALSE,
xWarningActive := FALSE,
xWarningConfirmPending := FALSE,
xErrorActive := _xErrorActive,
xErrorConfirmPending := FALSE);]]></ST>
</Implementation>
<Action Name="SM_AUTO" Id="{b5166e16-4fea-442b-9560-02c156f9a9ad}">
<Implementation>
<ST><![CDATA[CASE _iState OF
0: // Idle
// Check for all in auto mode
IF (NOT _xStringsInAutoMode) AND (NOT _fbNoAutomaticModeAlarm.bRaised) THEN
_fbNoAutomaticModeAlarm.Raise(0);
END_IF
// Wait for power command
IF ((ABS(_rAutoPowerRequest) > DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) OR GVL_MODBUS.stModbusEMSComm.stModbusReg12.wBMSControlsRegister.stBitmap.bScheduleActive)
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 <= 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;
_xReleaseInverterPower := TRUE;
// Check if the battery should still be active
IF (_rAutoPowerRequest = 0.0) AND NOT GVL_MODBUS.stModbusEMSComm.stModbusReg12.wBMSControlsRegister.stBitmap.bScheduleActive 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
_xReleaseInverterPower := FALSE;
_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
_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
_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
_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;
_xReleaseInverterPower := FALSE;
_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
]]></ST>
</Implementation>
</Action>
<Action Name="SM_BALANCING" Id="{f1f90032-de29-468d-899c-50bfb54e48e0}">
<Implementation>
<ST><![CDATA[CASE _iStateBalancing OF
0: // Test
IF GVL_SCADA.stAutomaticModeHMI.stStartAutoButton.xRequest THEN
_xReleaseInverterPower := FALSE;
_iStateBalancing := 5;
END_IF
// GVL_SCADA.stAutomaticModeHMI.stStartAutoButton.xRelease := (NOT _xStringsErrorActive) AND _afbStrings[_uiDebugCurrentString].xAllModulesInAutoMode;
5: // Check for start conditions
IF (NOT _xStringsErrorActive) AND _xStringsAllInAutomaticMode THEN
_xCanChangeMode := FALSE;
_xEnableString := FALSE;
_xStartBalancing := TRUE;
_iStateBalancing := 10;
END_IF
10: // Wait for balancing to be done
IF _xStringsBalancingDone THEN
_xCanChangeMode := TRUE;
_xStartBalancing := FALSE;
_iStateBalancing := 0;
END_IF
IF _xStringsErrorActive THEN
_iStateBalancing := 900;
_xStartBalancing := FALSE;
END_IF
IF GVL_SCADA.stAutomaticModeHMI.stStopAutoButton.xRequest THEN
_xStartBalancing := FALSE;
_xCanChangeMode := TRUE;
_iStateBalancing := 0;
END_IF
900: // Error state
IF _xConfirmAlarms AND (NOT _xStringsErrorActive) THEN
_xCanChangeMode := TRUE;
_iStateBalancing := 0;
END_IF
END_CASE]]></ST>
</Implementation>
</Action>
<Action Name="SM_CAPACITY_TEST" Id="{705978cf-2798-4a38-8f24-148e2ec1d46e}">
<Implementation>
<ST><![CDATA[]]></ST>
</Implementation>
</Action>
<Action Name="SM_MANUAL" Id="{ddef276e-9f4f-4258-b863-d254dd94b701}">
<Implementation>
<ST><![CDATA[_xCanChangeMode := TRUE;
_xReleaseInverterPower := FALSE;]]></ST>
</Implementation>
</Action>
<Action Name="SM_PRECHARGE" Id="{b84aedc8-0039-40a2-8abe-a166eca7bebc}">
<Implementation>
<ST><![CDATA[// Start on start button pressed
IF GVL_SCADA.stAutomaticModeHMI.stStartAutoButton.xRequest THEN
// Only start if everything is ok
IF _xStringsAllInAutomaticMode AND (NOT _xStringsErrorActive) THEN
_xStartPrecharge := TRUE;
END_IF
END_IF
// Sto pif stop button pressed
IF GVL_SCADA.stAutomaticModeHMI.stStopAutoButton.xRequest THEN
_xStartPrecharge := FALSE;
END_IF
// State machine
CASE _iStatePrecharge OF
0: // Idle
// Wait for start command
IF _xStartPrecharge AND _xStringsAllInAutomaticMode THEN
_xEnableString := TRUE;
_iStatePrecharge := 10;
_xReleaseInverterPower := FALSE;
_rPowerInverter := 0.0;
_xCanChangeMode := FALSE;
END_IF
10: // Wait for string to be ready
IF _xStringsReady AND (NOT _xStringsErrorActive) THEN
_iStatePrecharge := 30;
END_IF
// Shutdown
IF NOT _xStartPrecharge THEN
_xEnableString := FALSE;
_iStatePrecharge := 0;
_xCanChangeMode := TRUE;
END_IF
// Check for errors
IF _xStringsErrorActive THEN
_iStatePrecharge := 1000;
END_IF
30: // String enabled and dc circuit breaker closed
// Check if the battery should still be active
IF (NOT _xStartPrecharge) THEN
// Start string shutdown
_xEnableString := FALSE;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.UNDEFINED;
GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0;
_iStatePrecharge := 45;
_xCanChangeMode := TRUE;
END_IF
// Check for errors
IF _xStringsErrorActive THEN
_xEnableString := FALSE;
_iStatePrecharge := 1000;
END_IF
45: // Wait for shutdown of string to be done
IF _xStringsOff THEN
_iStatePrecharge := 0;
END_IF
IF _xStartPrecharge THEN
_iStatePrecharge := 0;
END_IF
// Check for errors
IF _xStringsErrorActive THEN
_xEnableString := FALSE;
_iStatePrecharge := 1000;
END_IF
1000: // Error state
_xEnableString := FALSE;
_rPowerInverter := 0.0;
_xCanChangeMode := TRUE;
_xStartPrecharge := FALSE;
_iStatePrecharge := 1010;
1010: // Wait for reset from error state
IF (NOT _xStringsErrorActive) AND (NOT _xStartPrecharge) THEN
// Goto init state
_iStatePrecharge := 0;
_xCanChangeMode := TRUE;
END_IF
END_CASE]]></ST>
</Implementation>
</Action>
<Action Name="SM_SAFETY_CHECK" Id="{6d8e5993-cf32-4980-9ea3-c1fbfa4b8601}">
<Implementation>
<ST><![CDATA[// Start on start button pressed
IF GVL_SCADA.stAutomaticModeHMI.stStartAutoButton.xRequest THEN
// Only start if everything is ok
IF _xStringsAllInAutomaticMode AND (NOT _xStringsErrorActive) THEN
_xStartSafetyCheck := TRUE;
END_IF
END_IF
// Stop if stop button pressed
IF GVL_SCADA.stAutomaticModeHMI.stStopAutoButton.xRequest THEN
_xStartSafetyCheck := FALSE;
END_IF
// State machine
CASE _iStateSafetyCheck OF
0: // Idle
// Wait for start command
IF _xStartSafetyCheck AND _xStringsAllInAutomaticMode THEN
_xEnableString := TRUE;
_xReleaseInverterPower := FALSE;
_iStateSafetyCheck := 10;
_rPowerInverter := 0.0;
_xCanChangeMode := FALSE;
END_IF
10: // Wait for string to be ready
IF _xStringsReady AND (NOT _xStringsErrorActive) THEN
_iStateSafetyCheck := 30;
END_IF
// Shutdown
IF NOT _xStartSafetyCheck THEN
_xEnableString := FALSE;
_iStateSafetyCheck := 0;
_xCanChangeMode := TRUE;
END_IF
// Check for errors
IF _xStringsErrorActive THEN
_iStateSafetyCheck := 1000;
END_IF
30: // String enabled and dc circuit breaker closed
// Check if the battery should still be active
IF (NOT _xStartSafetyCheck) THEN
// Start string shutdown
_xEnableString := FALSE;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.UNDEFINED;
GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0;
_iStateSafetyCheck := 45;
_xCanChangeMode := TRUE;
END_IF
// Check for errors
IF _xStringsErrorActive THEN
_xEnableString := FALSE;
_iStateSafetyCheck := 1000;
END_IF
45: // Wait for shutdown of string to be done
IF _xStringsOff THEN
_iStateSafetyCheck := 0;
END_IF
IF _xStartSafetyCheck THEN
_iStateSafetyCheck := 0;
END_IF
// Check for errors
IF _xStringsErrorActive THEN
_xEnableString := FALSE;
_iStateSafetyCheck := 1000;
END_IF
1000: // Error state
_xEnableString := FALSE;
_rPowerInverter := 0.0;
_xCanChangeMode := TRUE;
_xStartSafetyCheck := FALSE;
_iStateSafetyCheck := 1010;
1010: // Wait for reset from error state
IF (NOT _xStringsErrorActive) AND (NOT _xStartSafetyCheck) THEN
// Goto init state
_iStateSafetyCheck := 0;
_xCanChangeMode := TRUE;
END_IF
END_CASE]]></ST>
</Implementation>
</Action>
</POU>
</TcPlcObject>