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; // State for startup state machine _iStateStartup : INT := 0; // Startup busy flag _xStartupBusy : BOOL; // Internal power command _rPowerInternal : REAL := 0; // Enum for requested state _eRequestedState : (OFF := 1, STANDBY := 8, GRID_PRE_CONNECTED := 10, GRID_CONNECTED := 11) := OFF; // 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#500MS; // Time for setting the current power _timSetPowerDelay : TIME := T#250MS; // Timer for polling of current values _tonPollingTimer : TON; _tTimeoutPolling : TIME := T#5S; // Timer for setting the inverter power _tonSetPowerTimer : TON; // Timer for watchdog _tonWatchdogResetTimer : TON := (PT := T#1S); _tTimeoutWriteWatchdogRegister : TIME := T#5S; // Inverter alarm _fbErrorInverterAlarm : 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; // Error during cyclic reading _xErrorCyclicData : BOOL; // Internal inverter error _xErrorInverter : 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 HandleHeartbeat(); HandleCyclicData(); CASE _iState OF 0: // Pre-init phase (no battery limits set) _fbTONSetBatteryLimits(IN := TRUE); IF _fbTONSetBatteryLimits.Q THEN _fbTONSetBatteryLimits(IN := FALSE); _eRequestedState := 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 := 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 := REAL_TO_INT((_rPowerInternal*100) / (_rWMax * EXPT(10,_iWSetPctSF))); ELSE _iWSetPct := 0; END_IF ELSE _eRequestedState := OFF; _iWSetPct := 0; END_IF // Comm error or Watchdog error occured IF _xErrorCyclicData OR (NOT xHeartbeatOk) THEN _iWSetPct := 0; _eRequestedState := 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 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 _xErrorCyclicData) AND (NOT _xErrorInverter) THEN _eRequestedState := OFF; xError := 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]]> , bError=> , nErrId=> ); IF (NOT _fbWriteRequestedState.bBusy) AND _fbWriteRequestedState.bError THEN _xErrorCyclicData := TRUE; END_IF // 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:= _tonPollingTimer.Q AND (NOT _fbWritePowerCommand.bBusy), tTimeout:= _tTimeoutPolling, bBusy=> , bError=> , nErrId=> ); IF (NOT _fbWritePowerCommand.bBusy) AND _fbWritePowerCommand.bError THEN _xErrorCyclicData := TRUE; END_IF // Read current state _fbReadCurrentState( sIPAddr:= sInverterIPAddr, nTCPPort:= 502, nUnitID:= 16#01, nQuantity:= 1, nMBAddr:= CURRENT_STATE_REGISTER, cbLength:= SIZEOF(_eCurrentState), pDestAddr:= ADR(_eCurrentState), bExecute:= _tonPollingTimer.Q AND (NOT _fbReadCurrentState.bBusy), tTimeout:= _tTimeoutPolling, bBusy=> , bError=> , nErrId=> , cbRead=> ); IF (NOT _fbReadCurrentState.bBusy) AND _fbReadCurrentState.bError THEN _xErrorCyclicData := TRUE; END_IF IF _eCurrentState = E_KACO_CURRENT_STATE.GRID_CONNECTED OR _eCurrentState = E_KACO_CURRENT_STATE.THROTTLED THEN xActive := TRUE; ELSE xActive := FALSE; END_IF // 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:= _tonPollingTimer.Q AND (NOT _fbReadPCUState.bBusy), tTimeout:= _tTimeoutPolling, bBusy=> , bError=> , nErrId=> , cbRead=> ); IF (NOT _fbReadPCUState.bBusy) AND _fbReadPCUState.bError THEN _xErrorCyclicData := TRUE; END_IF IF (_stPCUState.ePCUState = E_KACO_PCU_STATE.ERROR) OR (_stPCUState.ePCUError <> E_KACO_PCU_ERROR.NO_EVENT) THEN _xErrorInverter := TRUE; ELSE _xErrorInverter := FALSE; END_IF // 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:= _tonPollingTimer.Q AND (NOT _fbReadDCValues.bBusy), tTimeout:= _tTimeoutPolling, bBusy=> , bError=> , nErrId=> , cbRead=> ); // Check if reading modbus register is done IF (NOT _fbReadDCValues.bBusy) THEN // 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; END_IF END_IF // 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:= _tonPollingTimer.Q AND (NOT _fbReadACValues.bBusy), tTimeout:= _tTimeoutPolling, bBusy=> , bError=> , nErrId=> , cbRead=> ); // Check if reading mudbus register is done IF (NOT _fbReadACValues.bBusy) THEN // 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; END_IF END_IF // Reset polling timer IF _tonPollingTimer.Q THEN _tonPollingTimer(IN := FALSE); END_IF]]> , bError=> , nErrId=> ); // Because there is no heartbeat register to read, // we will use a successfull write as a valid heartbeat signal IF _fbWriteHearbeatRegister.bError THEN xHeartbeatOk := FALSE; xError := TRUE; END_IF // Reset timer IF _tonWatchdogResetTimer.Q THEN _tonWatchdogResetTimer(IN := FALSE); 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#FF, // 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#FF, // 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#FF, // 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#FF, // 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#FF, // 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#FF, // 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]]>