0 = off // 3 - Max charge voltage // 4 - Max chrage current // 5 - Charge cutoff amp -> 0 = off _auiBatteryLimitValues : ARRAY[0..5] OF UINT := [6600, 300, 0, 9600, 300, 0]; // Current state _iState : INT := 0; // current state for cyclic data read _iStateCyclicData : INT := 0; // State for startup state machine _iStateStartup : INT := 0; // Startup busy flag _xStartupBusy : BOOL; // Internal power command _rPowerInternal : REAL := 0; _rLastPowerInternal : REAL := 0; // Enum for requested state _eRequestedState : E_KACO_PCU_REQUESTED_STATE := E_KACO_PCU_REQUESTED_STATE.OFF; _eLastRequestedState : E_KACO_PCU_REQUESTED_STATE := E_KACO_PCU_REQUESTED_STATE.UNDEFINED; // Watchdog timeout in seconds _uiWatchdogTimeoutSeconds : UINT := 10; // FB for reading Modbus holding registers _fbReadRegisters : FB_MBReadRegs; // FB for writing Modbus holding registers _fbWriteRegisters : FB_MBWriteRegs; // FB for writing heartbeat register _fbWriteHearbeatRegister : FB_MBWriteSingleReg; // FB for writing requested state _fbWriteRequestedState : FB_MBWriteSingleReg; // FB for writing current power command // Write multiple registers is used here // because FB_MBWriteSingleReg expects an // unsigned data type _fbWritePowerCommand : FB_MBWriteRegs; // FB for reading current state _fbReadCurrentState : FB_MBReadRegs; // FB for reading pcu state register _fbReadPCUState : FB_MBReadRegs; // FB for reading dc values _fbReadDCValues : FB_MBReadRegs; // FB for reading ac values _fbReadACValues : FB_MBReadRegs; // Time for polling for current dc values and check for inverter error _timPollingDelay : TIME := T#200MS; // Timer for polling of current values _tonPollingTimer : TON; _tTimeoutPolling : TIME := T#5S; // watchdog write timeout _tTimeoutWriteWatchdogRegister : TIME := T#5S; // Inverter alarm _fbErrorInverterAlarm : FB_TcAlarm; // Error when reading cyclic data _fbCyclicDataAlarm : FB_TcAlarm; // Error when reading heartbeat _fbHeartBeatAlarm : FB_TcAlarm; // Flag if battery limits have been set _xBatteryLimitsSet : BOOL := FALSE; // Flag to see if an error occured during setting the battery limits _xErrorSetBatteryLimits : BOOL := FALSE; // Battery limit scaling factors _arBattScalingFactors : ARRAY[0..1] OF INT; // Helper variable for writing a 1 to a register _uiEnableLimit : UINT := 1; // Retry timer to set battery limits _fbTONSetBatteryLimits : TON := (PT := T#5S); // Inverter power output _iWSetPct : INT := 0; // Converter max power scaling factor _iWMaxSF : INT; // Scaling factor for power setpoint _iWSetPctSF : INT := -2; // Scaled converter max power _rWMax : REAL; // Unscaled converter max power _uiWMax : UINT; // Current DC values (DCA, DCA_SF, DCV, DCV_SF, DCW, DCW_SF) in word array for efficient modbus reading _awCurrentDCValues : ARRAY[0..5] OF WORD; // Current AC values (W, W_SF, Hz, Hz_SF, VA, VA_SF, VAr, VAr_SF, PF, PF_SF) in word array for efficient modbus reading _awCurrentACValues : ARRAY[0..21] OF WORD; // Current state _eCurrentState : E_KACO_CURRENT_STATE; // Current PCU state and alarm messages _stPCUState : ST_KACU_PCU; // Current PCU state and alarm messages _stPCUStateDebug : ST_KACU_PCU; // Current PCU state and alarm messages _stPCUStateDebug2 : ST_KACU_PCU; _iCurrentErrorCountHB : UDINT := 0; // Error count since last successfull read on writeRequestedState _iErrorCountHB : UDINT := 0; // Total error count on writeRequestedState _iErrorIDHB : UDINT := 0; // Error ID on writeRequestedState _iCurrentErrorCountWRS : UDINT := 0; // Error count since last successfull read on writeRequestedState _iErrorCountWRS : UDINT := 0; // Total error count on writeRequestedState _iErrorIDWRS : UDINT := 0; // Error ID on writeRequestedState _iCurrentErrorCountWPC : UDINT := 0; // Error count since last successfull read on writePowerCommand _iErrorCountWPC : UDINT := 0; // Total error count on writePowerCommand _iErrorIDWPC : UDINT := 0; // Error ID on writePowerCommand _iCurrentErrorCountRCS : UDINT := 0; // Error count since last successfull read on readCurrentState _iErrorCountRCS : UDINT := 0; // Total error count on readCurrentState _iErrorIDRCS : UDINT := 0; // Error ID on readCurrentState _iCurrentErrorCountRPCUS : UDINT := 0; // Error count since last successfull read on readPCUState _iErrorCountRPCUS : UDINT := 0; // Total error count on readPCUState _iErrorIDRPCUS : UDINT := 0; // Error ID on readPCUState _iErrorCountRDCV : UDINT := 0; // Total error count on readDCValues _iErrorIDRDCV : UDINT := 0; // Error ID on readDCValues _iErrorCountRACV : UDINT := 0; // Total error count on readACValues _iErrorIDRACV : UDINT := 0; // Error ID on readACValues _xResetCounter : BOOL := FALSE; // Reset error counter // Error during cyclic reading _xErrorCyclicData : BOOL; _xErrorCyclicDataLedge : BOOL; // cyclic data ledge _xHeartBeatNOK : BOOL; // heartbeat error ledge // Internal inverter error _xErrorInverter : BOOL; // Inverterfault (introduced by NA-Schutz) _xFaultInverter : BOOL; // Inverter name for alarm message _sName : STRING; END_VAR VAR CONSTANT // Battery limits registers (Model 64202) // 41120 is Voltage and 41121 is amp BATTERY_LIMIT_SF_START : WORD := 41120; BATTERY_SET_LIMITS_START : WORD := 41122; (* DIS_MIN_V : WORD := 41122; DIS_MAX_A : WORD := 41123; CHA_MAX_V : WORD := 41125; CHA_MAX_A : WORD := 41126; *) EN_LIMIT : WORD := 41129; // Power registers (Model 64201) W_SET_PCT : WORD := 41069; // Basic settings registers (Model 121) W_MAX : WORD := 40214; W_MAX_SF : WORD := 40234; // Start of register with the current dc values // Size 4 DC_VALUES_START_REGISTER : WORD := 40097; // Start of register with the current ac values // SIZE 10 AC_VALUES_START_REGISTER : WORD := 40072; // Inverter statemachine status register // Size 1, enum16 (Range = 0 .. 65534, Not implemented = 0xFFFF) PCU_STATUS_START_REGISTER : WORD := 41078; // Inverter current state CURRENT_STATE_REGISTER : WORD := 41065; // Control register to set the target state of the inverters state machine // Size 1, enum16 (Range = 0 .. 65534, Not implemented = 0xFFFF) REQUESTED_STATE_REGISTER : WORD := 41064; // Hearbeat register WATCHDOG_REGISTER : WORD := 41068; END_VAR ]]> rMaxBattPower) THEN _rPowerInternal := rMaxBattPower; END_IF IF (rPower < -rMaxBattPower) THEN _rPowerInternal := -rMaxBattPower; END_IF HandleCyclicData(); // Copy inverter states to output stInverterInfos.eRequestedState := _eRequestedState; stInverterInfos.eCurrentState := _eCurrentState; stInverterInfos.ePCUState := _stPCUState.ePCUState; stInverterInfos.ePCUError := _stPCUState.ePCUError; // Run state machine CASE _iState OF 0: // Pre-init phase (no battery limits set) _fbTONSetBatteryLimits(IN := TRUE); IF _fbTONSetBatteryLimits.Q THEN _fbTONSetBatteryLimits(IN := FALSE); _eRequestedState := E_KACO_PCU_REQUESTED_STATE.OFF; _iStateStartup := 0; _iState := 10; END_IF 10: // Try to set battery limits SetBatteryLimits(); IF (NOT _xStartupBusy) THEN // Battery limits set and no error IF _xBatteryLimitsSet AND (NOT _xErrorSetBatteryLimits) THEN _iWSetPct := 0; _iState := 20; END_IF // If there was an error settings the battery limits, retry IF _xErrorSetBatteryLimits THEN _iState := 0; END_IF END_IF 20: // Read max power scaling _fbReadRegisters( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, // 16#FF for Modbus TCP nQuantity:= 1, nMBAddr:= W_MAX_SF, cbLength:= SIZEOF(_iWMaxSF), pDestAddr:= ADR(_iWMaxSF), bExecute:= TRUE, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> , cbRead=> ); // Check if reading mudbus register is done IF NOT _fbReadRegisters.bBusy THEN // If there was no error then continue IF NOT _fbReadRegisters.bError THEN _iState := 30; // Check for valid value IF (_iWMaxSF < -10) OR (_iWMaxSF > 10) OR (_iWMaxSF = 16#8000) THEN // Goto error state _iState := 1000; END_IF ELSE xError := TRUE; // Goto error state _iState := 1000; END_IF _fbReadRegisters(bExecute := FALSE); END_IF 30: // Read max power _fbReadRegisters( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, // 16#FF for Modbus TCP nQuantity:= 1, nMBAddr:= W_MAX, cbLength:= SIZEOF(_uiWMax), pDestAddr:= ADR(_uiWMax), bExecute:= TRUE, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> , cbRead=> ); // Check if reading mudbus register is done IF NOT _fbReadRegisters.bBusy THEN // If there was no error then continue IF NOT _fbReadRegisters.bError THEN _iState := 40; // Calculate WMax // Reading a register with scaling factor = value * 10^SF _rWMax := LREAL_TO_REAL(_uiWMax * EXPT(10,_iWMaxSF)); ELSE xError := TRUE; // Goto error state _iState := 1000; END_IF _fbReadRegisters(bExecute := FALSE); END_IF 40: // Idle state, wait for enable // If enable and INTLK Ok IF xEnable THEN _eRequestedState := E_KACO_PCU_REQUESTED_STATE.GRID_CONNECTED; IF xReleasePower THEN // Calculate power to write to register // (could not find where the scaling for wset can be read but its -2!) // => 10% = 1000 // Writing a register with scaling factor = value / (10^SF) //_iWSetPct := LREAL_TO_INT((_rPowerInternal*100)/(_rWMax * EXPT(10,-2))); _iWSetPct := LREAL_TO_INT((_rPowerInternal*100) / (_rWMax * EXPT(10,_iWSetPctSF))); ELSE _iWSetPct := 0; END_IF ELSE _eRequestedState := E_KACO_PCU_REQUESTED_STATE.OFF; _iWSetPct := 0; END_IF // Comm error or Watchdog error occured IF _xErrorCyclicData OR (NOT xHeartbeatOk) THEN _iWSetPct := 0; _eRequestedState := E_KACO_PCU_REQUESTED_STATE.OFF; _iState := 1000; END_IF // Dont set inverter into off state when an internal error occured // because this will reset the error message IF _xErrorInverter OR _xErrorCyclicData OR (NOT xHeartbeatOk) THEN _iWSetPct := 0; _iState := 1000; END_IF 1000: // Error state xError := TRUE; _iState := 1001; 1001: // Error state, wait for reset //IF xReset AND (NOT xEnable) AND (NOT _xErrorInverter) AND (NOT _xErrorCyclicData) AND xHeartbeatOk THEN IF xReset AND (NOT xEnable) AND (NOT _xErrorCyclicData) AND xHeartbeatOk THEN _eRequestedState := E_KACO_PCU_REQUESTED_STATE.OFF; xError := FALSE; _xErrorInverter := FALSE; _xFaultInverter := FALSE; _xErrorCyclicDataLedge := FALSE; _xHeartBeatNOK := FALSE; _iState := 0; END_IF END_CASE // =============================== // Inverter alarm handling // =============================== IF xError AND (NOT _fbErrorInverterAlarm.bRaised) THEN _fbErrorInverterAlarm.Raise(0); END_IF IF (NOT xError) AND _fbErrorInverterAlarm.bRaised THEN _fbErrorInverterAlarm.Clear(0, FALSE); END_IF IF (_fbErrorInverterAlarm.eConfirmationState = TcEventConfirmationState.WaitForConfirmation) AND xReset THEN _fbErrorInverterAlarm.Confirm(0); END_IF]]> _eLastRequestedState THEN _iStateCyclicData := 10; ELSE _iStateCyclicData := 12; END_IF END_IF 10: // Write requested state _fbWriteRequestedState( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, nMBAddr:= REQUESTED_STATE_REGISTER, nValue:= INT_TO_WORD(_eRequestedState), bExecute:= TRUE, tTimeout:= _tTimeoutPolling, bBusy=> , bError=> , nErrId=> ); IF (NOT _fbWriteRequestedState.bBusy) THEN _fbWriteRequestedState(bExecute := FALSE); _iStateCyclicData := 11; IF _fbWriteRequestedState.bError THEN _iCurrentErrorCountWRS := _iCurrentErrorCountWRS + 1; _iErrorCountWRS := _iErrorCountWRS + 1; _iErrorIDWRS := _fbWriteRequestedState.nErrId; ELSE _iCurrentErrorCountWRS := 0; _eLastRequestedState := _eRequestedState; END_IF END_IF 11: // wait for next state _tonPollingTimer(IN := TRUE, PT := _timPollingDelay); IF _tonPollingTimer.Q THEN _tonPollingTimer(IN := FALSE); _iStateCyclicData := 12; END_IF 12: // Check if powercommand changed IF _rPowerInternal <> _rLastPowerInternal THEN _iStateCyclicData := 20; ELSE _iStateCyclicData := 21; END_IF 20: // Write current power command _fbWritePowerCommand( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, nQuantity := 1, nMBAddr:= W_SET_PCT, cbLength := SIZEOF(_iWSetPct), pSrcAddr:= ADR(_iWSetPct), bExecute:= TRUE, tTimeout:= _tTimeoutPolling, bBusy=> , bError=> , nErrId=> ); IF (NOT _fbWritePowerCommand.bBusy) THEN _fbWritePowerCommand(bExecute := FALSE); _iStateCyclicData := 21; IF _fbWritePowerCommand.bError THEN _iCurrentErrorCountWPC := _iCurrentErrorCountWPC + 1; _iErrorCountWPC := _iErrorCountWPC + 1; _iErrorIDWPC := _fbWritePowerCommand.nErrId; ELSE _rLastPowerInternal := _rPowerInternal; _iCurrentErrorCountWPC := 0; END_IF END_IF 21: // wait for next state _tonPollingTimer(IN := TRUE, PT := _timPollingDelay); IF _tonPollingTimer.Q THEN _tonPollingTimer(IN := FALSE); _iStateCyclicData := 30; END_IF 30: // Read current state _fbReadCurrentState( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, nQuantity:= 1, nMBAddr:= CURRENT_STATE_REGISTER, cbLength:= SIZEOF(_eCurrentState), pDestAddr:= ADR(_eCurrentState), bExecute:= TRUE, tTimeout:= _tTimeoutPolling, bBusy=> , bError=> , nErrId=> , cbRead=> ); IF (NOT _fbReadCurrentState.bBusy) THEN _fbReadCurrentState(bExecute := FALSE); _iStateCyclicData := 31; IF _fbReadCurrentState.bError THEN _iCurrentErrorCountRCS := _iCurrentErrorCountRCS + 1; _iErrorCountRCS := _iErrorCountRCS + 1; _iErrorIDRCS := _fbReadCurrentState.nErrId; ELSE _iCurrentErrorCountRCS := 0; END_IF END_IF 31: // wait for next state _tonPollingTimer(IN := TRUE, PT := _timPollingDelay); IF _tonPollingTimer.Q THEN _tonPollingTimer(IN := FALSE); _iStateCyclicData := 40; END_IF 40: // Read current pcu status _fbReadPCUState( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, nQuantity:= 2, nMBAddr:= PCU_STATUS_START_REGISTER, cbLength:= SIZEOF(_stPCUState), pDestAddr:= ADR(_stPCUState), bExecute:= TRUE, tTimeout:= _tTimeoutPolling, bBusy=> , bError=> , nErrId=> , cbRead=> ); IF (NOT _fbReadPCUState.bBusy) THEN _fbReadPCUState(bExecute := FALSE); _iStateCyclicData := 41; IF _fbReadPCUState.bError THEN _iCurrentErrorCountRPCUS := _iCurrentErrorCountRPCUS + 1; _iErrorCountRPCUS := _iErrorCountRPCUS + 1; _iErrorIDRPCUS := _fbReadPCUState.nErrId; IF _iCurrentErrorCountRPCUS >= GVL_CONFIG.udiMaxConsecutiveInvError THEN _xErrorCyclicData := TRUE; END_IF ELSE _iCurrentErrorCountRPCUS := 0; END_IF END_IF 41: // wait for next state _tonPollingTimer(IN := TRUE, PT := _timPollingDelay); IF _tonPollingTimer.Q THEN _tonPollingTimer(IN := FALSE); _iStateCyclicData := 50; END_IF 50: // Read current dc values _fbReadDCValues( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, // 16#FF for Modbus TCP nQuantity:= 6, nMBAddr:= DC_VALUES_START_REGISTER, cbLength:= SIZEOF(_awCurrentDCValues), pDestAddr:= ADR(_awCurrentDCValues), bExecute:= TRUE, tTimeout:= _tTimeoutPolling, bBusy=> , bError=> , nErrId=> , cbRead=> ); // Check if reading modbus register is done IF (NOT _fbReadDCValues.bBusy) THEN _fbReadDCValues(bExecute := FALSE); _iStateCyclicData := 51; // If there was no error and the converter has no error continue IF (NOT _fbReadDCValues.bError) THEN stCurrentValues.rActDCCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentDCValues[0]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[1]))); stCurrentValues.rActDCVoltage := LREAL_TO_REAL(WORD_TO_UINT(_awCurrentDCValues[2]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[3]))); stCurrentValues.rActDCPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentDCValues[4]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[5]))); ELSE // Dont throw comm error here because this is just // informational data and not process critical stCurrentValues.rActDCCurrent := 0.0; stCurrentValues.rActDCVoltage := 0.0; stCurrentValues.rActDCPower := 0.0; _iErrorCountRDCV := _iErrorCountRDCV + 1; _iErrorIDRDCV := _fbReadDCValues.nErrId; END_IF END_IF 51: // wait for next state _tonPollingTimer(IN := TRUE, PT := _timPollingDelay); IF _tonPollingTimer.Q THEN _tonPollingTimer(IN := FALSE); _iStateCyclicData := 60; END_IF 60: // Read current ac values _fbReadACValues( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, // 16#FF for Modbus TCP nQuantity:= 22, nMBAddr:= AC_VALUES_START_REGISTER, cbLength:= SIZEOF(_awCurrentACValues), pDestAddr:= ADR(_awCurrentACValues), bExecute:= TRUE, tTimeout:= _tTimeoutPolling, bBusy=> , bError=> , nErrId=> , cbRead=> ); // Check if reading mudbus register is done IF (NOT _fbReadACValues.bBusy) THEN _fbReadACValues(bExecute := FALSE); _iStateCyclicData := 61; // If there was no error and the converter has no error continue IF (NOT _fbReadACValues.bError) THEN stCurrentValues.rActACCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[0]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4]))); stCurrentValues.rActtACPhaseACurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[1]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4]))); stCurrentValues.rActtACPhaseBCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[2]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4]))); stCurrentValues.rActtACPhaseCCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[3]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4]))); stCurrentValues.rActACPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[12]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[13]))); stCurrentValues.rActACFreq := LREAL_TO_REAL(WORD_TO_UINT(_awCurrentACValues[14]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[15]))); stCurrentValues.rActApparentPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[16]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[17]))); stCurrentValues.rActReactivePower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[18]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[19]))); stCurrentValues.rActPowerFactor := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[20]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[21]))); ELSE // Dont throw comm error here because this is just // informational data and not process critical stCurrentValues.rActACCurrent := 0.0; stCurrentValues.rActtACPhaseACurrent := 0.0; stCurrentValues.rActtACPhaseBCurrent := 0.0; stCurrentValues.rActtACPhaseCCurrent := 0.0; stCurrentValues.rActACPower := 0.0; stCurrentValues.rActACFreq := 0.0; stCurrentValues.rActApparentPower := 0.0; stCurrentValues.rActReactivePower := 0.0; stCurrentValues.rActPowerFactor := 0.0; _iErrorCountRACV := _iErrorCountRACV + 1; _iErrorIDRACV := _fbReadACValues.nErrId; END_IF END_IF 61: // wait for next state _tonPollingTimer(IN := TRUE, PT := _timPollingDelay); IF _tonPollingTimer.Q THEN _tonPollingTimer(IN := FALSE); _iStateCyclicData := 70; END_IF 70: // Timeout should be less than timer interval _fbWriteHearbeatRegister( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, nMBAddr:= WATCHDOG_REGISTER, nValue:= _uiWatchdogTimeoutSeconds, bExecute:= TRUE, tTimeout:= _tTimeoutWriteWatchdogRegister, bBusy=> , bError=> , nErrId=> ); // Because there is no heartbeat register to read, // we will use a successfull write as a valid heartbeat signal IF NOT _fbWriteHearbeatRegister.bBusy THEN _fbWriteHearbeatRegister(bExecute := FALSE); _iStateCyclicData := 0; IF _fbWriteHearbeatRegister.bError THEN _iCurrentErrorCountHB := _iCurrentErrorCountHB + 1; _iErrorCountHB := _iErrorCountHB + 1; _iErrorIDHB := _fbWriteHearbeatRegister.nErrId; IF _iCurrentErrorCountHB >= GVL_CONFIG.udiMaxConsecutiveInvError THEN xHeartbeatOk := FALSE; xError := TRUE; END_IF ELSE _iCurrentErrorCountHB := 0; xHeartbeatOk := TRUE; END_IF END_IF END_CASE // evaluate inverter state IF _eCurrentState = E_KACO_CURRENT_STATE.GRID_CONNECTED OR _eCurrentState = E_KACO_CURRENT_STATE.THROTTLED THEN xActive := TRUE; ELSE xActive := FALSE; END_IF // evaluate inverter errors IF ((_stPCUState.ePCUState = E_KACO_PCU_STATE.ERROR) OR (_stPCUState.ePCUError <> E_KACO_PCU_ERROR.NO_EVENT)) AND (_stPCUState.ePCUError <> 11) THEN // ignore undervoltage error when not enabled _stPCUStateDebug := _stPCUState; IF NOT xReleasePower AND _stPCUState.ePCUError = E_KACO_PCU_ERROR.UNDER_VOLT THEN _xErrorInverter := FALSE; ELSE _xErrorInverter := TRUE; _stPCUStateDebug2 := _stPCUState; END_IF ELSE _xErrorInverter := FALSE; END_IF IF _eCurrentState = E_KACO_CURRENT_STATE.FAULT THEN// AND xReleasePower THEN IF (NOT xReleasePower) AND _stPCUState.ePCUError = E_KACO_PCU_ERROR.UNDER_VOLT THEN _xErrorInverter := FALSE; ELSE _xErrorInverter := TRUE; END_IF END_IF // evaluate modbus errors IF _iCurrentErrorCountRPCUS >= GVL_CONFIG.udiMaxConsecutiveInvError OR _iCurrentErrorCountRCS >= GVL_CONFIG.udiMaxConsecutiveInvError OR _iCurrentErrorCountWPC >= GVL_CONFIG.udiMaxConsecutiveInvError OR _iCurrentErrorCountWRS >= GVL_CONFIG.udiMaxConsecutiveInvError OR _iCurrentErrorCountHB >= GVL_CONFIG.udiMaxConsecutiveInvError THEN _xErrorCyclicData := TRUE; ELSE _xErrorCyclicData := FALSE; END_IF // set fault flag when error active IF _xErrorCyclicData THEN _xErrorCyclicDataLedge := TRUE; END_IF // handle cyclic data alarm IF _xErrorCyclicData AND NOT _fbCyclicDataAlarm.bRaised THEN _fbCyclicDataAlarm.Raise(); END_IF IF NOT _xErrorCyclicData AND _fbCyclicDataAlarm.bRaised THEN _fbCyclicDataAlarm.Clear(0, FALSE); END_IF IF _fbCyclicDataAlarm.eConfirmationState = TcEventConfirmationState.WaitForConfirmation AND xReset THEN _fbCyclicDataAlarm.Confirm(0); END_IF // set fault flag when error active IF NOT xHeartbeatOk THEN _xHeartBeatNOK := TRUE; END_IF // handle heartbeat alarm IF NOT xHeartbeatOk AND NOT _fbHeartBeatAlarm.bRaised THEN _fbHeartBeatAlarm.Raise(); END_IF IF xHeartbeatOk AND _fbHeartBeatAlarm.bRaised THEN _fbHeartBeatAlarm.Clear(0, FALSE); END_IF IF _fbHeartBeatAlarm.eConfirmationState = TcEventConfirmationState.WaitForConfirmation AND xReset THEN _fbHeartBeatAlarm.Confirm(0); END_IF]]> , bError=> , nErrId=> , cbRead=> ); // Check if reading mudbus register is done IF NOT _fbReadRegisters.bBusy THEN IF (NOT _fbReadRegisters.bError) THEN _iStateStartup := 20; ELSE // Goto error state //_xErrorSetBatteryLimits := TRUE; _iStateStartup := 1000; END_IF _fbReadRegisters(bExecute := FALSE); END_IF 20: // Set battery limits _fbWriteRegisters( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, // 16#FF for Modbus TCP nQuantity:= 6, nMBAddr:= BATTERY_SET_LIMITS_START, cbLength:= SIZEOF(_auiBatteryLimitValues), pSrcAddr:= ADR(_auiBatteryLimitValues), bExecute:= TRUE, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> ); // If writing modbus register is done IF NOT _fbWriteRegisters.bBusy THEN // And there is no error, then continue IF (NOT _fbWriteRegisters.bError) THEN _iStateStartup := 60; ELSE // Goto error state _iStateStartup := 1000; END_IF _fbWriteRegisters(bExecute := FALSE); END_IF (* 20: // Set min voltage _fbWriteRegister( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, // 16#FF for Modbus TCP nQuantity:= 1, nMBAddr:= DIS_MIN_V, cbLength:= SIZEOF(uiMinDisVoltage), pSrcAddr:= ADR(uiMinDisVoltage), bExecute:= TRUE, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> ); // If writing modbus register is done IF NOT _fbWriteRegister.bBusy THEN // And there is no error, then continue IF (NOT _fbWriteRegister.bError) THEN _iStateStartup := 30; ELSE // Goto error state //_xErrorSetBatteryLimits := TRUE; _iStateStartup := 1000; END_IF _fbWriteRegister(bExecute := FALSE); END_IF 30: // Set max voltage _fbWriteRegister( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, // 16#FF for Modbus TCP nQuantity:= 1, nMBAddr:= CHA_MAX_V, cbLength:= SIZEOF(uiMaxChaVoltage), pSrcAddr:= ADR(uiMaxChaVoltage), bExecute:= TRUE, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> ); // If writing modbus register is done IF NOT _fbWriteRegister.bBusy THEN // And there is no error, then continue IF (NOT _fbWriteRegister.bError) THEN _iStateStartup := 40; ELSE // Goto error state //_xErrorSetBatteryLimits := TRUE; _iStateStartup := 1000; END_IF _fbWriteRegister(bExecute := FALSE); END_IF 40: // Set charge current _fbWriteRegister( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, // 16#FF for Modbus TCP nQuantity:= 1, nMBAddr:= CHA_MAX_A, cbLength:= SIZEOF(uiMaxChaCurrent), pSrcAddr:= ADR(uiMaxChaCurrent), bExecute:= TRUE, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> ); // If writing modbus register is done IF NOT _fbWriteRegister.bBusy THEN // And there is no error, then continue IF (NOT _fbWriteRegister.bError) THEN _iStateStartup := 50; ELSE // Goto error state _xErrorSetBatteryLimits := TRUE; _iStateStartup := 1000; END_IF _fbWriteRegister(bExecute := FALSE); END_IF 50: // Set discharge current _fbWriteRegister( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, // 16#FF for Modbus TCP nQuantity:= 1, nMBAddr:= DIS_MAX_A, cbLength:= SIZEOF(uiMaxDisCurrent), pSrcAddr:= ADR(uiMaxDisCurrent), bExecute:= TRUE, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> ); // If writing modbus register is done IF NOT _fbWriteRegister.bBusy THEN // And there is no error, then continue IF (NOT _fbWriteRegister.bError) THEN _iStateStartup := 60; ELSE // Goto error state _xErrorSetBatteryLimits := TRUE; _iStateStartup := 1000; END_IF _fbWriteRegister(bExecute := FALSE); END_IF *) 60: // Enable battery limits _fbWriteRegisters( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, // 16#FF for Modbus TCP nQuantity:= 1, nMBAddr:= EN_LIMIT, cbLength:= SIZEOF(_uiEnableLimit), pSrcAddr:= ADR(_uiEnableLimit), bExecute:= TRUE, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> ); // If writing modbus register is done IF NOT _fbWriteRegisters.bBusy THEN // And there is no error, then continue IF (NOT _fbWriteRegisters.bError) THEN _iStateStartup := 70; ELSE // Goto error state //_xErrorSetBatteryLimits := TRUE; _iState := 1000; END_IF _fbWriteRegisters(bExecute := FALSE); END_IF 70: // Battery limits set _xBatteryLimitsSet := TRUE; _xStartupBusy := FALSE; 1000: // Error state _xErrorSetBatteryLimits := TRUE; _xBatteryLimitsSet := FALSE; _xStartupBusy := FALSE; END_CASE]]>