<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4026.12">
  <POU Name="FB_PowerSupplyKaco" Id="{43c28077-20d6-4076-bde1-bc92c785654f}" SpecialFunc="None">
    <Declaration><![CDATA[FUNCTION_BLOCK FB_PowerSupplyKaco
VAR_INPUT
	sInverterIPAddr : STRING;
	xEnable : BOOL;
	rPower : REAL;
	xReset : BOOL;
	rMaxBattPower : REAL := 40_000; // 24kW
	// Discharge min DC voltage (V)
	uiMinDisVoltage : UINT := 6000;
	
	// Charge max voltage (V)
	uiMaxChaVoltage : UINT := 9600;
	
	// Discharge max current (A)
	uiMaxDisCurrent : UINT := 300;
	
	// Charge max current (A)
	uiMaxChaCurrent : UINT := 300;
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;
	
	// State for startup state machine
	_iStateStartup : INT := 0;
	
	// Startup busy flag
	_xStartupBusy : BOOL;
	
	// Internal power command
	_rPowerInternal : REAL := 0;
	
	// Value for commanding the target state of the inverter
	_uiPCSSetOperation : UINT;
	
	// FB for reading Modbus holding registers
	_fbReadRegister : FB_MBReadRegs;
	
	// FB for writing Modbus holding registers
	_fbWriteRegister : FB_MBWriteRegs;
	
	// 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;
	
	// Inverter alarm
	_fbErrorInverterAlarm : FB_TcAlarm;
	
	// Flag if battery limits have been set
	_xBatteryLimitsSet : BOOL := FALSE;
	
	// Flag to start setting the battery limits
	_xSetBatteryLimits : 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#1S);
	
	// Inverter power output
	_iWSetPct : INT := 0;
	
	// Converter max power scaling factor
	_iWMaxSF : INT;
	
	// 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 of the inverters internal statemachine
	_uiInverterState : UINT;
	
	// Polling in enable state
	_xEnableInternal : BOOL;
	
	// Inverter name for alarm message
	_sName : STRING;
END_VAR
VAR CONSTANT
	// Battery limits registers (Model 64202)
	BATTERY_LIMIT_SF : WORD := 41120;
	DIS_MIN_V : WORD := 41122;
	CHA_MAX_V : WORD := 41125;
	CHA_MAX_A : WORD := 41126;
	DIS_MAX_A : WORD := 41123;
	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)
	STATUS_REGISTER : WORD := 40108;
	
	// 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 := 41064;
END_VAR

]]></Declaration>
    <Implementation>
      <ST><![CDATA[_rPowerInternal := rPower;

// Clamp rPower to maximum allowed power
IF (rPower > rMaxBattPower) THEN
	_rPowerInternal := rMaxBattPower;
END_IF

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

CASE _iState OF
	0: // Pre-init phase (no battery limits set)
		_fbTONSetBatteryLimits(IN := TRUE);
		IF _fbTONSetBatteryLimits.Q THEN
			_fbTONSetBatteryLimits(IN := FALSE);
			_iState := 10;
		END_IF
		
	10: // Read scaling factors
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#01, // 16#FF for Modbus TCP
			nQuantity:= 2, 
			nMBAddr:= BATTERY_LIMIT_SF, 
			cbLength:= SIZEOF(_arBattScalingFactors), 
			pDestAddr:= ADR(_arBattScalingFactors), 
			bExecute:= TRUE, 
			tTimeout:= T#30S, 
			bBusy=> , 
			bError=> , 
			nErrId=> , 
			cbRead=> );
			
		// Check if reading mudbus register is done
		IF NOT _fbReadRegister.bBusy THEN
			IF (NOT _fbReadRegister.bError) THEN
				_iState := 20;
			ELSE
				// Goto error state
				_iState := 0;
			END_IF
			_fbReadRegister(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
				_iState := 30;
			ELSE
				// Goto error state
				_iState := 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
				_iState := 40;
			ELSE
				// Goto error state
				_iState := 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
				_iState := 50;
			ELSE
				// Goto error state
				_xErrorSetBatteryLimits := TRUE;
				_iState := 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
				_iState := 60;
			ELSE
				// Goto error state
				_xErrorSetBatteryLimits := TRUE;
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
	
	60: // Enable battery limits
		_fbWriteRegister(
			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 _fbWriteRegister.bBusy THEN
			// And there is no error, then continue
			IF (NOT _fbWriteRegister.bError) THEN
				// Battery limits set
				_xBatteryLimitsSet := TRUE;
				_xStartupBusy := FALSE;
				_iState := 70;
			ELSE
				// Goto error state
				//_xErrorSetBatteryLimits := TRUE;
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
	70: // Try to set battery limits
		_rPowerInternal := 0;
		_iWSetPct := 0;
		_iState := 80;

	80: // Battery limits are set, write zero power
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#01, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= W_SET_PCT, 
			cbLength:= SIZEOF(_iWSetPct), 
			pSrcAddr:= ADR(_iWSetPct), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> );
			
		IF NOT _fbWriteRegister.bBusy THEN
			IF (NOT _fbWriteRegister.bError) THEN
				_iState := 90;
			ELSE
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
	90: // Read max power scaling
		_fbReadRegister(
			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 _fbReadRegister.bBusy THEN
			// If there was no error then continue
			IF NOT _fbReadRegister.bError THEN
				_iState := 100;
				// 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;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		
	100: // Read max power
		_fbReadRegister(
			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 _fbReadRegister.bBusy THEN
			// If there was no error then continue
			IF NOT _fbReadRegister.bError THEN
				_iState := 110;
				// 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
			_fbReadRegister(bExecute := FALSE);
		END_IF
	
		
	110: // Idle state, wait for enable
		IF _tonPollingTimer.Q THEN
			_tonPollingTimer(IN := FALSE, PT := _timPollingDelay);
			_iState := 120;
		END_IF
		
		// If enable and INTLK Ok
		IF xEnable THEN
			// _iState := 130;
			_iState := 130;
			_rPowerInternal := 0.0;
			_iWSetPct := 0;
			_uiPCSSetOperation := 11;
			_tonPollingTimer(IN := FALSE, PT := _timPollingDelay);
		ELSE
			_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
			_rPowerInternal := 0.0;
			_iWSetPct := 0;
		END_IF
		
		
	120: // Write current power demand
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#01, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= W_SET_PCT, 
			cbLength:= SIZEOF(_iWSetPct), 
			pSrcAddr:= ADR(_iWSetPct), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> );
			
		IF NOT _fbWriteRegister.bBusy THEN
			IF (NOT _fbWriteRegister.bError) THEN
				_iState := 121;
			ELSE
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		

	121: // Read current dc values
		_fbReadRegister(
			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:= 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 := 122;
				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
				xError := TRUE;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		
	122: // Read current ac values
		_fbReadRegister(
			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:= 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 := 123;
				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
				// Read error register
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		
	123: // Read current status
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#01, // 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
// 				IF (NOT xEnable) THEN
// 					_iState := 110;
// 				ELSE
// 					_iState := 140;
// 				END_IF
			_iState := 124;
			ELSE
				_iState := 1000;
			END_IF
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
	124: // Reset if in error state and not enabled
			// Inverter in error state
			IF (_uiInverterState = 7) THEN
				// Inverter not enabled, so try a reset
				IF (NOT xEnable) THEN
					_uiPCSSetOperation := 1;
					_iState := 125;
				ELSE
					// Else go to error state
					_iState := 1000;
				END_IF
			ELSE
				// No error and not enabled
				IF (NOT _xEnableInternal) THEN
					// Goto polling state
					_iState := 110;
				ELSE
					// No error and enabled, goto enabled polling state
					_iState := 140;
				END_IF
			END_IF
				
				
	125: // Send off/reset command
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#01, // 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 := 110;
			ELSE
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
		
	130: // Go to started
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#01, // 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 := 133;
			ELSE
				_uiPCSSetOperation := 1;
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
		
	131: // Wait for inverter to be online and in state Grid Connected(11)
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#01, // 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 Grid Connected(11) then continue
			IF NOT _fbReadRegister.bError AND _uiInverterState = 8 THEN
				_uiInverterState := 11;
				_iState := 133;
			END_IF
			
			// If the inverter is not ready wait some time before polling again
			IF NOT _fbReadRegister.bError AND _uiInverterState <> 8 THEN
				_iState := 132;
			END_IF
			
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		// If not enable, go back to idle
		IF (NOT xEnable) THEN
			_fbReadRegister(bExecute := FALSE);
			_iState := 110;
		END_IF
		
	132: // Delay polling inverter ready
		_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
		IF _tonPollingTimer.Q THEN
			_tonPollingTimer(IN := FALSE);
			_iState := 131;
		END_IF
		
		// If not enable, go back to idle
		IF (NOT xEnable) THEN
			_tonPollingTimer(IN := FALSE);
			_iState := 110;
		END_IF
		
	
	133: // Standby mode to grid connect
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#01, // 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 Grid Connected(11) then continue
			IF NOT _fbReadRegister.bError AND _uiInverterState = 4 THEN
				//_uiPCSSetOperation := 11;
				xActive := TRUE;
				_iState := 140;
			END_IF
			
			// If the inverter is not ready wait some time before polling again
			IF NOT _fbReadRegister.bError AND _uiInverterState <> 4 THEN
				_iState := 134;
			END_IF
			
			_fbReadRegister(bExecute := FALSE);
		END_IF
		
		// If not enable, go back to idle
// 		IF (NOT xEnable) THEN
// 			_fbReadRegister(bExecute := FALSE);
// 			_iState := 110;
// 		END_IF
		
	134: // Polling timer delay
		_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
		IF _tonPollingTimer.Q THEN
			_tonPollingTimer(IN := FALSE);
			_iState := 133;
		END_IF
		
		// If not enable, go back to idle
		IF (NOT xEnable) THEN
			_tonPollingTimer(IN := FALSE);
			_iState := 110;
		END_IF
	
	140: // Enabled
		_tonPollingTimer(IN := TRUE);
		IF _tonPollingTimer.Q THEN
			_tonPollingTimer(IN := FALSE, PT := _timPollingDelay);
			
			// Calculate power to write to register
			_iWSetPct := LREAL_TO_INT((_rPowerInternal*100)/(_rWMax * EXPT(10,-2)));
			
			_iState := 120;
		END_IF
		
		// If enable and INTLK Ok
		IF (NOT xEnable) THEN
			_rPowerInternal := 0.0;
			_iWSetPct := 0;
			_uiPCSSetOperation := 1;
			_tonPollingTimer(IN := FALSE, PT := _timPollingDelay);
			_iState := 150;
		END_IF
		
	150: // Send shutdown power command
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#01, // 16#FF for Modbus TCP
			nQuantity:= 1, 
			nMBAddr:= W_SET_PCT, 
			cbLength:= SIZEOF(_iWSetPct), 
			pSrcAddr:= ADR(_iWSetPct), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			bError=> , 
			nErrId=> );
			
		IF NOT _fbWriteRegister.bBusy THEN
			IF (NOT _fbWriteRegister.bError) THEN
				_iState := 151;
			ELSE
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
	151: // Goto off state
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#01, // 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
				xActive := FALSE;
				_iState := 110;
			ELSE
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		

	1000: // Error state
		xActive := FALSE;
		xError := TRUE;
		_xEnableInternal := TRUE;
		_iState := 1001;
		
	1001: // Error state, wait for reset
		IF xReset AND (NOT xEnable) THEN
			_uiPCSSetOperation := 1;
			xError := FALSE;
			_iState := 1002;
		END_IF
		
		
	1002: // Try to clear all error by going into off state
		_fbWriteRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#01, // 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
				xActive := FALSE;
				_iState := 110;
			ELSE
				// Goto error state
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
END_CASE
// 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)));]]></ST>
    </Implementation>
    <Method Name="FB_Init" Id="{5f7291f3-1517-49b9-b6a8-07debcc66730}">
      <Declaration><![CDATA[//FB_Init ist immer implizit verfügbar und wird primär für die Initialisierung verwendet.
//Der Rückgabewert wird nicht ausgewertet. Für gezielte Einflussnahme können Sie
//die Methoden explizit deklarieren und darin mit dem Standard-Initialisierungscode 
//zusätzlichen Code bereitstellen. Sie können den Rückgabewert auswerten.
METHOD FB_Init: BOOL
VAR_INPUT    
	bInitRetains: BOOL; // TRUE: Die Retain-Variablen werden initialisiert (Reset warm / Reset kalt)    
	bInCopyCode: BOOL;  // TRUE: Die Instanz wird danach in den Kopiercode kopiert (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>
    <Property Name="Name" Id="{1af22804-e4c4-4295-b5b9-5968e747d45b}">
      <Declaration><![CDATA[PROPERTY Name : STRING]]></Declaration>
      <Get Name="Get" Id="{6338c761-e06b-4a94-a0d3-0502e3ee997d}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[Name := _sName;]]></ST>
        </Implementation>
      </Get>
      <Set Name="Set" Id="{eebb6389-e8f3-42a9-a08a-6e1cad8f0192}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[_sName := Name;

_fbErrorInverterAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
        </Implementation>
      </Set>
    </Property>
    <Action Name="SetBatteryLimits" Id="{15c86a66-2f5b-42ab-82c5-3aeebcab0e43}">
      <Implementation>
        <ST><![CDATA[CASE _iStateStartup OF
	0: // Start
		IF _xSetBatteryLimits THEN
			_xSetBatteryLimits := FALSE;
			_xBatteryLimitsSet := FALSE;
			_xErrorSetBatteryLimits := FALSE;
			_xStartupBusy := TRUE;
			_iStateStartup := 10;
		END_IF
		
	10: // Read scaling factors
		_fbReadRegister(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#FF, // 16#FF for Modbus TCP
			nQuantity:= 2, 
			nMBAddr:= BATTERY_LIMIT_SF, 
			cbLength:= SIZEOF(_arBattScalingFactors), 
			pDestAddr:= ADR(_arBattScalingFactors), 
			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 := 20;
			ELSE
				// Goto error state
				//_xErrorSetBatteryLimits := TRUE;
				_iState := 1000;
			END_IF
			_fbReadRegister(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
				_iState := 30;
			ELSE
				// Goto error state
				//_xErrorSetBatteryLimits := TRUE;
				_iState := 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
				_iState := 40;
			ELSE
				// Goto error state
				//_xErrorSetBatteryLimits := TRUE;
				_iState := 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
				_iState := 50;
			ELSE
				// Goto error state
				_xErrorSetBatteryLimits := TRUE;
				_iState := 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
				_iState := 60;
			ELSE
				// Goto error state
				_xErrorSetBatteryLimits := TRUE;
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
	
	60: // Enable battery limits
		_fbWriteRegister(
			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 _fbWriteRegister.bBusy THEN
			// And there is no error, then continue
			IF (NOT _fbWriteRegister.bError) THEN
				// Battery limits set
				_xBatteryLimitsSet := TRUE;
				_xStartupBusy := FALSE;
				_iState := 0;
			ELSE
				// Goto error state
				//_xErrorSetBatteryLimits := TRUE;
				_iState := 1000;
			END_IF
			_fbWriteRegister(bExecute := FALSE);
		END_IF
		
	1000: // Error state
		_iStateStartup := 0;
		_xErrorSetBatteryLimits := TRUE;
		_xBatteryLimitsSet := FALSE;
		_xStartupBusy := FALSE;
END_CASE]]></ST>
      </Implementation>
    </Action>
  </POU>
</TcPlcObject>