<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4026.12">
  <POU Name="FB_PowerSupplySunspec" Id="{a826dd09-442c-45c5-8ae3-9b71f293003c}" SpecialFunc="None">
    <Declaration><![CDATA[FUNCTION_BLOCK FB_PowerSupplySunspec
VAR_INPUT
	sInverterIPAddr : STRING;
	xEnable : BOOL;
	rPower : REAL;
	xReset : BOOL;
	rMaxBattPower : REAL := 40_000; // 24kW
END_VAR
VAR_OUTPUT	
	// Inverter active
	xActive : BOOL;
	
	// FB error
	xError : BOOL;
	
	// Heartbeat ok signal
	xHeartbeatOk : BOOL := TRUE;

	// Current inverter values
	stCurrentValues : ST_SUNSPEC_CURRENT_VALUES;
END_VAR
VAR
	// Current state
	_iState : INT := 0;
	
	// Internal power command
	_rPowerInternal : REAL;
	
	// FB for reading Modbus holding registers
	_fbReadRegister : FB_MBReadRegs;
	
	// FB for writing Modbus holding registers
	_fbWriteRegister : FB_MBWriteRegs;
	
	// FB for writing Modbus holding registers for inverter heartbeat
	_fbWriteHeartbeatRegister : FB_MBWriteRegs;
	
	// FB for reading Modbus holding registers for inverter heartbeat
	_fbReadHeartbeatRegister : FB_MBReadRegs;
	
	// Timer for checking if the inverter started in a reasonable amount of time
	//_tonInverterStartup : TON;
	
	// converter max power scaling factor
	_iWMaxSF : INT;
	
	// Unscaled converter max power
	_uiWMax : UINT;
	
	// Scaled converter max power
	_rWMax : REAL;
	
	// Unscaled limit for converter power
	_iWMaxLimPct : INT;
	
	// Scaling factor for reactive power percent value
	_iVarPctSF : INT;
	
	// Reread set power limit
	//_iWMaxLimPctRead : INT;
	//_rWMaxLimPctReadScaled : REAL;
	
	// Scaling factor for power limiting
	_iWMaxLimPctSF : INT;
	
	// Unscaled maximum power from type label
	//_iWRTGSF : INT;
	
	// Scaling for maximum power from type label
	//_rWRTGScaling : REAL;
	
	// Current state of the inverters internal statemachine
	_uiInverterState : UINT;
	
	// Value to enable or dissable the Power limiting feature
	_uiMaxLimEn : UINT;
	
	// Value for commanding the target state of the inverter
	_uiPCSSetOperation : UINT;
	
	// Maximum reactive power
	_iMaxPowerVar : INT := 0;
	
	// Enable max reactive power percent controller
	_iMaxVarPct : INt := 1;
	
	// Holds the state number in which an error occured
	_iErrorInState : INT;
	
	// 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;
	
	// Timer for setting the inverter power
	_tonSetPowerTimer : TON;
	
	// Timer for incrementing heartbeat signal
	_tonHearbeatIncTimer : TON := (PT := T#500ms); 
	
	// 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;
	
	// Inverter error bits
	_dwErrorBits : DWORD;
	
	// Inverter reset errors command
	_uiResetInverter : UINT := 1;
	
	// PLC -> Inverter heartbeat
	_uiPLCToInverterCounter : UINT;
	
	// Inverter -> PLC heartbeat
	_uiInverterToPLCCounter : UINT;
	_uiInverterToPLCCounterOld : UINT;
	
	// Flag to check if inverter has incremented the heartbeat counter
	_xInverterHBCounterIncremented : BOOL := TRUE;
	
	// Inverter alarm
	_fbErrorInverterAlarm : FB_TcAlarm;
	
	// Heartbeat timeout
	_fbHeartbeatTimeout : TON;
	
	_sName : STRING;
	
	_uiSetPowerLimitErrorCounter : UINT;
	_uiLastSetPowerLimitErrorCounter : UINT;
	
END_VAR
VAR CONSTANT
	// Inverter statemachine status register
	// Size 1, enum16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
	STATUS_REGISTER : WORD := 40108;
	
	// Throttled power register
	// Size 1, int16 (Range = -32767 .. 32767, Not implemented 0x8000)
	W_MAX_LIM_PCT_REGISTER : WORD := 40187;
	
	// Throttled power register scaling factor
	// Size 1, sunssf (int16) (Range = -10 .. 10, Not implemented 0x8000)
	W_MAX_LIM_PCT_SF_REGISTER : WORD := 40205;
	
	// Control register to enable and dissable if the power throttleing should be active
	// Size1, enum16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
	W_MAX_LIM_EN_REGISTER : WORD := 40191; 
	
	// Register to reset latched alarms in the inverter
	// Size 1, uint16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
	PCS_ALARM_RESET_REGISTER : WORD := 40230;
	
	// Control register to set the target state of the inverters state machine
	// Size 1, enum16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
	PCS_SET_OPERATION_REGISTER : WORD := 40231;
	
	// Maximum inverter output power
	// Size 1, uint16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
	W_MAX_REGISTER : WORD := 40152;
	
	// Maximum inverter output power scaling factor
	// Size 1, sunssf (int16) (Range = -10 .. 10, Not implemented 0x8000)
	W_MAX_SF_REGISTER : WORD := 40172;
	
	// Maximum inverter output power from type label
	// Size 1, uint16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
	//W_RTG_REGISTER : WORD := 40125;
	
	// Maximum inverter output power from type label scaling factor
	// Size 1, sunssf (int16) (Range = -10 .. 10, Not implemented 0x8000)
	//W_RTG_SF_REGISTER : WORD := 40126;
	
	// 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;
	
	// Power factor register in cosine of angle
	// Size 1, int16 (Range = -32767 .. 32767, Not implemented 0x8000)
	//OUT_PF_SET : WORD := 40192;
	
	// Enable power factor controller
	// Size 1, enum16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
	//OUT_PF_SET_ENA : WORD := 40196;
	
	// Reactive power in percent of W_Max
	// Size 1, int16 (Range = -32767 .. 32767, Not implemented 0x8000)
	VAR_W_MAX_PCT : WORD := 40197;
	
	// Enable percent limited var controller
	// Size 1, enum16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
	VAR_PCT_ENA : WORD := 40204;
	
	// Register for reactive power percent scaling factor
	// Size 1, sunssf (int16) (Range = -10 .. 10, Not implemented 0x8000)
	VAR_PCT_SF : WORD := 40207;
	
	// Error bits register
	// Size 2 
	EVT_1_REGISTER : WORD := 40110;
	
	// PLC -> Inverter Heartbeat register
	CONTROLLER_HB : WORD := 40229;
	
	// Inverter -> PLC heartbeat register
	PCS_HB : WORD := 40228;
END_VAR
]]></Declaration>
    <Implementation>
      <ST><![CDATA[// Clamp rPower to maximum allowed power
IF (rPower > rMaxBattPower) THEN
	rPower := rMaxBattPower;
END_IF

IF (rPower < -rMaxBattPower) THEN
	rPower := -rMaxBattPower;
END_IF

HandleHeartbeat();

// State machine
CASE _iState OF
	
	0: // Off
		IF _tonPollingTimer.Q THEN
			_tonPollingTimer(IN := FALSE, PT := _timPollingDelay);
			_iState := 1;
		END_IF
		
		// If enable and INTLK Ok
		IF xEnable THEN
			_iState := 10;
			_rPowerInternal := 0.0;
			_tonPollingTimer(IN := FALSE, PT := _timPollingDelay);
		ELSE
			_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
		END_IF
		
	1: // Read inverter status
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= STATUS_REGISTER, 
			cbLength:= SIZEOF(_uiInverterState), 
			pDestAddr:= ADR(_uiInverterState), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
			
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			IF NOT _fbReadRegister.bError THEN
				_iState := 2;
			ELSE
				_iErrorInState := _iState;
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		
	2: // IF inverter is not in STANDYB(8) STATE, send command to shutdown inverter
		IF (_uiInverterState = 8) OR (_uiInverterState = 1) THEN
			_iState := 3;
		ELSIF _uiInverterState = 7 THEN
			_iState := 990;
		ELSE
			_uiPCSSetOperation := 3;
			_iState := 200;
		END_IF
		
		
	3: // Read current DC values
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 6, 
			nMBAddr:= DC_VALUES_START_REGISTER, 
			cbLength:= SIZEOF(_awCurrentDCValues), 
			pDestAddr:= ADR(_awCurrentDCValues), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
		
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			// If there was no error and the converter has no error continue
			IF NOT _fbReadRegister.bError THEN
				_iState := 4;
				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
				_iErrorInState := _iState;
				// Read error register
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		
	4: // Read current ac values
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 22, 
			nMBAddr:= AC_VALUES_START_REGISTER, 
			cbLength:= SIZEOF(_awCurrentACValues), 
			pDestAddr:= ADR(_awCurrentACValues), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
		
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			// If there was no error and the converter has no error continue
			IF NOT _fbReadRegister.bError THEN
				// Go back to polling state
				_iState := 0;
				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
				_iErrorInState := _iState;
				// Read error register
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		
	10: // Wait for inverter to be online and in state STANDBY(8)
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= STATUS_REGISTER, 
			cbLength:= SIZEOF(_uiInverterState), 
			pDestAddr:= ADR(_uiInverterState), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
			
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			// If there was no error and the state is STANDBY(8) then continue
			IF NOT _fbReadRegister.bError AND _uiInverterState = 8 THEN
				_iState := 20;
			END_IF
			
			// If the inverter is not ready wait some time before polling again
			IF NOT _fbReadRegister.bError AND _uiInverterState <> 8 THEN
				_iState := 15;
			END_IF
			
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		// If not enable, go back to idle
		IF NOT xEnable THEN
			_fbReadRegister(bExecute := FALSE);
			_iState := 0;
		END_IF
		
		
	15: // Delay polling inverter ready
		_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
		IF _tonPollingTimer.Q THEN
			_tonPollingTimer(IN := FALSE);
			_iState := 10;
		END_IF
		
		// If not enable, go back to idle
		IF NOT xEnable THEN
			_tonPollingTimer(IN := FALSE);
			_iState := 0;
		END_IF
		
	
	20: // Read inverter max power scaling
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= W_MAX_SF_REGISTER, 
			cbLength:= SIZEOF(_iWMaxSF), 
			pDestAddr:= ADR(_iWMaxSF), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
		
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			// If there was no error then continue
			IF NOT _fbReadRegister.bError THEN
				_iState := 25;
				// Check for valid value
				IF (_iWMaxSF < -10) OR (_iWMaxSF > 10) OR (_iWMaxSF = 16#8000) THEN
					ADSLOGSTR(msgCtrlMask := ADSLOG_MSGTYPE_HINT, msgFmtStr := 'FBInverter into error state from: %s', strArg := TO_STRING(_iState));
					// Goto error state
					_iState := 1000;
				END_IF
			ELSE
				xError := TRUE;
				_iErrorInState := _iState;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		
	25: // Read inverter Max power limit scaling
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= W_MAX_LIM_PCT_SF_REGISTER, 
			cbLength:= SIZEOF(_iWMaxLimPctSF), 
			pDestAddr:= ADR(_iWMaxLimPctSF), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
		
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			// If there was no error then continue
			IF NOT _fbReadRegister.bError THEN
				_iState := 30;
				// Check for valid value
				IF (_iWMaxLimPctSF < -10) OR (_iWMaxLimPctSF > 10) OR (_iWMaxLimPctSF = 16#8000) THEN
					// Goto error state
					_iState := 1000;
				END_IF
			ELSE
				xError := TRUE;
				_iErrorInState := _iState;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		
	26: // Read inverter scaling factor for reactive power
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= VAR_PCT_SF, 
			cbLength:= SIZEOF(_iVarPctSF), 
			pDestAddr:= ADR(_iVarPctSF), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
			
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			// If there was no error then continue
			IF NOT _fbReadRegister.bError THEN
				_iState := 30;
				// Check for valid value
				IF (_iVarPctSF < -10) OR (_iVarPctSF > 10) OR (_iVarPctSF = 16#8000) THEN
					// Goto error state
					_iState := 1000;
				END_IF
			ELSE
				xError := TRUE;
				_iErrorInState := _iState;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		
	30: // Read inverter max power
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= W_MAX_REGISTER, 
			cbLength:= SIZEOF(_uiWMax), 
			pDestAddr:= ADR(_uiWMax), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
		
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			// If there was no error then continue
			IF NOT _fbReadRegister.bError THEN
				_iState := 40;
				// Reading a register with scaling factor = value * 10^SF
				_rWMax := LREAL_TO_REAL(_uiWMax * EXPT(10,_iWMaxSF));
				
				// Calculate power to write to register
				_iWMaxLimPct := LREAL_TO_INT((_rPowerInternal*100)/(_rWMax * EXPT(10,_iWMaxLimPctSF)));
			ELSE
				xError := TRUE;
				_iErrorInState := _iState;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
	
		
	40: // Set power limit
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= W_MAX_LIM_PCT_REGISTER, 
			cbLength:= SIZEOF(_iWMaxLimPct), 
			pSrcAddr:= ADR(_iWMaxLimPct), 
			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
				_iState := 50;
				_uiMaxLimEn := 1;
				_uiLastSetPowerLimitErrorCounter := _uiSetPowerLimitErrorCounter;
				_uiSetPowerLimitErrorCounter := 0;
				// Calculate reactive power setting
				//_iMaxPowerVar := LREAL_TO_INT((rReactivePower*100)/(_iMaxPowerVar * EXPT(10,_iVarPctSF)));
			ELSE
				_uiSetPowerLimitErrorCounter := _uiSetPowerLimitErrorCounter + 1;
				IF _uiSetPowerLimitErrorCounter > 5 THEN
					_iErrorInState := _iState;
					// Goto error state
					_iState := 1000;
				ELSE
					_iState := 41;
				END_IF
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
	41: // Retry send power limit delay
		_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
		IF _tonPollingTimer.Q THEN
			_tonPollingTimer(IN := FALSE);
			_iState := 40;
		END_IF
		
	42: // Set max reactive power in percent
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= VAR_W_MAX_PCT, 
			cbLength:= SIZEOF(_iMaxPowerVar), 
			pSrcAddr:= ADR(_iMaxPowerVar), 
			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
				_iState := 42;
			ELSE
				xError := TRUE;
				_iErrorInState := _iState;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
		
	43: // Enable reactive power percent limiting
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= VAR_PCT_ENA, 
			cbLength:= SIZEOF(_iMaxVarPct), 
			pSrcAddr:= ADR(_iMaxVarPct), 
			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
				_iState := 50;
			ELSE
				xError := TRUE;
				_iErrorInState := _iState;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
		
	50: // Enable Power limiting (THROTTLED)
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= W_MAX_LIM_EN_REGISTER, 
			cbLength:= SIZEOF(_uiMaxLimEn), 
			pSrcAddr:= ADR(_uiMaxLimEn), 
			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
				_iState := 60; // 51
				_uiPCSSetOperation := 1; // 4
				xActive := TRUE;
			ELSE
				xError := TRUE;
				_iErrorInState := _iState;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
		
	51: // Go to started
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= PCS_SET_OPERATION_REGISTER, 
			cbLength:= SIZEOF(_uiPCSSetOperation), 
			pSrcAddr:= ADR(_uiPCSSetOperation), 
			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
				//_uiPCSSetOperation := 1;
				_iState := 60;
			ELSE
				_uiPCSSetOperation := 1;
				_iErrorInState := _iState;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
		
	60: // Switch to THROTTLED mode
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= PCS_SET_OPERATION_REGISTER, 
			cbLength:= SIZEOF(_uiPCSSetOperation), 
			pSrcAddr:= ADR(_uiPCSSetOperation), 
			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
				_iState := 65;
			ELSE
				_iErrorInState := _iState;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
		
	65: // Wait for polling timer
		_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
		_tonSetPowerTimer(IN := TRUE, PT := _timSetPowerDelay);
		IF _tonPollingTimer.Q THEN
			_tonPollingTimer(IN := FALSE);
			_iState := 70;
		ELSIF _tonSetPowerTimer.Q AND xEnable THEN // (ABS(rPower - _rPowerInternal) > 0.1) AND
			_tonSetPowerTimer(IN := FALSE);
			_rPowerInternal := rPower;
			
			// Add power when derating is active
			IF (stCurrentValues.rActDCVoltage > 900) AND (rPower < 0) THEN
				_rPowerInternal := rPower - ((stCurrentValues.rActDCVoltage - 900.0) * 35.0);
			END_IF
			
			// Calculate power to write to register
			_iWMaxLimPct := LREAL_TO_INT((_rPowerInternal*100)/(_rWMax * EXPT(10,_iWMaxLimPctSF)));
			
			
			// If power has been changed, goto set power limit mode
			_iState := 40;
		END_IF
		
		// check if inverter should shut down
		IF (NOT xEnable) THEN
			_uiPCSSetOperation := 3;
			_rPowerInternal := 0.0;
			_iWMaxLimPct := 0;
			// Goto shutdown sequence
		 	_iState := 200;
		END_IF
		
		
	70: // Enabled, check for error
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= STATUS_REGISTER, 
			cbLength:= SIZEOF(_uiInverterState), 
			pDestAddr:= ADR(_uiInverterState), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
		
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			// If there was no error and the converter has no error continue
			IF (NOT _fbReadRegister.bError) AND (_uiInverterState <> 7) THEN
				_iState := 80;
				IF (_uiInverterState = 4) OR (_uiInverterState = 5) THEN
					xActive := TRUE;
				ELSE
					xActive := FALSE;
				END_IF
			ELSE
				xError := TRUE;
				xActive := FALSE;
				_uiPCSSetOperation := 3;
				_iErrorInState := _iState;
				ADSLOGDINT(msgCtrlMask:= ADSLOG_MSGTYPE_ERROR, msgFmtStr:= 'Fehler im state: %s', dintArg:= INT_TO_DINT(_iErrorInState));
				// Read error register
				_iState := 200;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		
	80: // Read current DC values
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 6, 
			nMBAddr:= DC_VALUES_START_REGISTER, 
			cbLength:= SIZEOF(_awCurrentDCValues), 
			pDestAddr:= ADR(_awCurrentDCValues), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
		
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			// If there was no error and the converter has no error continue
			IF NOT _fbReadRegister.bError THEN
				_iState := 85;
				stCurrentValues.rActDCCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentDCValues[0]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[1])));
				stCurrentValues.rActDCVoltage := LREAL_TO_REAL(WORD_TO_INT(_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
				_iErrorInState := _iState;
				// Read error register
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		
	85: // Read current ac values
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 22, 
			nMBAddr:= AC_VALUES_START_REGISTER, 
			cbLength:= SIZEOF(_awCurrentACValues), 
			pDestAddr:= ADR(_awCurrentACValues), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
		
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			// If there was no error and the converter has no error continue
			IF NOT _fbReadRegister.bError THEN
				// Go back to polling state
				_iState := 90;
				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
				_iErrorInState := _iState;
				// Read error register
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		
	90: // Read current inverter status
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= STATUS_REGISTER, 
			cbLength:= SIZEOF(_uiInverterState), 
			pDestAddr:= ADR(_uiInverterState), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
			
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			IF NOT _fbReadRegister.bError THEN
				_iState := 93;
				stCurrentValues.uiStatus := _uiInverterState;
			ELSE
				_iErrorInState := _iState;
				// Read error register
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
	93: // Send current power demand
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= W_MAX_LIM_PCT_REGISTER, 
			cbLength:= SIZEOF(_iWMaxLimPct), 
			pSrcAddr:= ADR(_iWMaxLimPct), 
			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
				_iState := 65;
				_uiMaxLimEn := 1;
				_uiLastSetPowerLimitErrorCounter := _uiSetPowerLimitErrorCounter;
				_uiSetPowerLimitErrorCounter := 0;
				// Calculate reactive power setting
				//_iMaxPowerVar := LREAL_TO_INT((rReactivePower*100)/(_iMaxPowerVar * EXPT(10,_iVarPctSF)));
			ELSE
				_uiSetPowerLimitErrorCounter := _uiSetPowerLimitErrorCounter + 1;
				IF _uiSetPowerLimitErrorCounter > 5 THEN
					_iErrorInState := _iState;
					// Goto error state
					_iState := 1000;
				ELSE
					_iState := 41;
				END_IF
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
		
	200: // Shutdown send zero power command
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= W_MAX_LIM_PCT_REGISTER, 
			cbLength:= SIZEOF(_iWMaxLimPct), 
			pSrcAddr:= ADR(_iWMaxLimPct), 
			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
				_iState := 201;
				_uiPCSSetOperation := 3;
			ELSE
				_iErrorInState := _iState;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
	201: // Shutdown sequence
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= PCS_SET_OPERATION_REGISTER, 
			cbLength:= SIZEOF(_uiPCSSetOperation), 
			pSrcAddr:= ADR(_uiPCSSetOperation), 
			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
				_iState := 210;
			ELSE
				_iErrorInState := _iState;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
	
		
	210: // Wait for poll timer to 
		_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
		IF _tonPollingTimer.Q THEN
			_tonPollingTimer(IN := FALSE);
			_iState := 220;
		END_IF
		
		
	220: // Poll and wait for standby state
		_iErrorInState := _iState;
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= STATUS_REGISTER, 
			cbLength:= SIZEOF(_uiInverterState), 
			pDestAddr:= ADR(_uiInverterState), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
		
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			// If there was no error and the converter has no error continue
			IF NOT _fbReadRegister.bError THEN
				_iState := 0;
				xActive := FALSE;
				//xCloseDCRelais := FALSE;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
	
		
	990: // Read error register
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 2, 
			nMBAddr:= EVT_1_REGISTER, 
			cbLength:= SIZEOF(_dwErrorBits), 
			pDestAddr:= ADR(_dwErrorBits), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
		
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			// If there was no error and the converter has no error continue
			IF NOT _fbReadRegister.bError THEN
				_iErrorInState := _iState;
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
	1000: // Write error state to log
		ADSLOGDINT(msgCtrlMask:= ADSLOG_MSGTYPE_ERROR, msgFmtStr:= 'Fehler im state: %s', dintArg:= INT_TO_DINT(_iErrorInState));
		_fbWriteRegister(bExecute := FALSE);
		_fbReadRegister(bExecute := FALSE);
		xActive := FALSE;
		xError := TRUE;
		_iState := 1001;
		
	1001: // Error state, wait for reset
		IF xReset AND xHeartbeatOk AND (NOT xEnable) THEN
			_iState := 1010;
		END_IF
		
		
	1010: // Try to clear all latched events
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= PCS_ALARM_RESET_REGISTER, 
			cbLength:= SIZEOF(_uiResetInverter), 
			pSrcAddr:= ADR(_uiResetInverter), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> );
			
		IF NOT _fbWriteRegister.bBusy THEN
			_iState := 0;
			xError := FALSE;
			_fbWriteRegister(bExecute := FALSE);
		END_IF
END_CASE


// ===============================
// Heartbeat check
// ===============================
_fbHeartbeatTimeout(IN := (NOT _xInverterHBCounterIncremented), PT := T#5S);

// Reset heartbeat ok signal
IF xReset AND (NOT _fbHeartbeatTimeout.Q) THEN
	xHeartbeatOk := TRUE;
END_IF

// Check for heartbeat
IF _fbHeartbeatTimeout.Q THEN
	xHeartbeatOk := FALSE;
END_IF

IF (NOT xHeartbeatOk) AND (_iState < 1000) THEN
	_iState := 1000;
END_IF

// ===============================
// 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]]></ST>
    </Implementation>
    <Method Name="FB_init" Id="{a80728a8-68c7-4f6a-87fc-246cb88104d4}">
      <Declaration><![CDATA[METHOD FB_init : BOOL
VAR_INPUT
	bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
	bInCopyCode : BOOL;  // if TRUE, the instance afterwards gets moved into the copy code (online change)
	
	sName : STRING;
END_VAR
]]></Declaration>
      <Implementation>
        <ST><![CDATA[_sName := sName;

// Create inverter main alarm
_fbErrorInverterAlarm.CreateEx(stEventEntry := TC_EVENTS.Inverter.InverterError, bWithConfirmation := TRUE, 0);
_fbErrorInverterAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
      </Implementation>
    </Method>
    <Action Name="HandleHeartbeat" Id="{323d78bf-2521-40eb-b5f1-47fb6b9e0ed1}">
      <Implementation>
        <ST><![CDATA[// Increment heartbeat counter and check current heartbeat
_tonHearbeatIncTimer(IN := TRUE);
IF _tonHearbeatIncTimer.Q THEN
	_tonHearbeatIncTimer(IN := FALSE);
	_uiPLCToInverterCounter := _uiPLCToInverterCounter + 1;
	IF (NOT _fbWriteHeartbeatRegister.bBusy) THEN
		_fbWriteHeartbeatRegister.bExecute := TRUE;
	END_IF
	IF (NOT _fbReadHeartbeatRegister.bBusy) THEN
		_fbReadHeartbeatRegister.bExecute := TRUE;
	END_IF
END_IF

// Write new heartbeat counter
_fbWriteHeartbeatRegister(
	sIPAddr:= sInverterIPAddr, 
	nTCPPort:= 502, 
	nUnitID:= 16#FF, // 16#FF for Modbus TCP
	nQuantity:= 1, 
	nMBAddr:= CONTROLLER_HB, 
	cbLength:= SIZEOF(_uiPLCToInverterCounter), 
	pSrcAddr:= ADR(_uiPLCToInverterCounter), 
	tTimeout:= T#5S, 
	bBusy=> , 
	bError=> , 
	nErrId=> );	
	
// If writing modbus register is done
IF (NOT _fbWriteHeartbeatRegister.bBusy) THEN
	_fbWriteHeartbeatRegister(bExecute := FALSE);
END_IF

// Read current heartbeat counter
_fbReadHeartbeatRegister(
	sIPAddr:= sInverterIPAddr, 
	nTCPPort:= 502, 
	nUnitID:= 16#FF, // 16#FF for Modbus TCP
	nQuantity:= 1, 
	nMBAddr:= PCS_HB, 
	cbLength:= SIZEOF(_uiInverterToPLCCounter), 
	pDestAddr:= ADR(_uiInverterToPLCCounter), 
	tTimeout:= T#5S, 
	bBusy=> , 
	bError=> , 
	nErrId=> , 
	cbRead=> );
			
	IF (NOT _fbReadHeartbeatRegister.bBusy) THEN
		_fbReadHeartbeatRegister(bExecute := FALSE);
		IF (NOT _fbReadHeartbeatRegister.bError) THEN
			// Check if counter has been incremented by the inverter
			IF (_uiInverterToPLCCounter - _uiInverterToPLCCounterOld) > 0 THEN
				_xInverterHBCounterIncremented := TRUE;
				
				// Safe old value
				_uiInverterToPLCCounterOld := _uiInverterToPLCCounter;
			ELSE
				_xInverterHBCounterIncremented := FALSE;
			END_IF
		ELSE
			// only flag heartbeat as incremented of modbusregister could be read
			_xInverterHBCounterIncremented := FALSE;
		END_IF
	END_IF]]></ST>
      </Implementation>
    </Action>
    <Property Name="Name" Id="{ef8c6e8f-7c1b-4781-b201-87f759acb289}">
      <Declaration><![CDATA[PROPERTY Name : string]]></Declaration>
      <Get Name="Get" Id="{bc17161e-727d-4abd-a845-a7eacc08f995}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[Name := _sName;]]></ST>
        </Implementation>
      </Get>
      <Set Name="Set" Id="{7fdecf53-efe5-43de-bc46-5f24fb6a7ffb}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[_sName := Name;

_fbErrorInverterAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
        </Implementation>
      </Set>
    </Property>
  </POU>
</TcPlcObject>