<?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;
	xReleasePower : 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
	// Battery limits
	// 0 - Min discharge voltage
	// 1 - Max discharge current
	// 2 - Discharge cutoff amp -> 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;
	
	// 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;
	
	// 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

]]></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

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 := LREAL_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 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
			_eRequestedState := OFF;
			xError := 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]]></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);

// Create inverter heartbeat alarm
_fbHeartBeatAlarm.CreateEx(stEventEntry := TC_EVENTS.Inverter.InverterHeartbeatError, bWithConfirmation := TRUE, 0);
_fbHeartBeatAlarm.ipArguments.Clear().AddString(_sName);

// Create inverter cyclic data alarm
_fbCyclicDataAlarm.CreateEx(stEventEntry := TC_EVENTS.Inverter.InverterCyclicError, bWithConfirmation := TRUE, 0);
_fbCyclicDataAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
      </Implementation>
    </Method>
    <Action Name="HandleCyclicData" Id="{4343583a-b80a-437e-8fc8-9963ab894fbc}">
      <Implementation>
        <ST><![CDATA[IF _xResetCounter THEN
	_xResetCounter := FALSE;
	_iCurrentErrorCountHB := 0;
	_iErrorCountHB := 0;
	_iErrorIDHB := 0;
	_iCurrentErrorCountWRS := 0; 
	_iErrorCountWRS := 0;
	_iErrorIDWRS := 0;
	_iCurrentErrorCountWPC := 0;
	_iErrorCountWPC := 0;
	_iErrorIDWPC := 0;
	_iCurrentErrorCountRCS := 0;
	_iErrorCountRCS := 0;
	_iErrorIDRCS := 0;
	_iCurrentErrorCountRPCUS := 0;
	_iErrorCountRPCUS := 0;
	_iErrorIDRPCUS := 0; 
	_iErrorCountRDCV := 0;
	_iErrorIDRDCV := 0; 
	_iErrorCountRACV := 0;
	_iErrorIDRACV := 0;
END_IF

// Fetch cyclic data with polling timer
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);

CASE _iStateCyclicData OF
	0: // init state
		IF _tonPollingTimer.Q THEN
			_tonPollingTimer(IN := FALSE);
			_iStateCyclicData := 10;
		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 := 20;
			IF _fbWriteRequestedState.bError THEN
				_iCurrentErrorCountWRS := _iCurrentErrorCountWRS + 1;
				_iErrorCountWRS := _iErrorCountWRS + 1;
				_iErrorIDWRS := _fbWriteRequestedState.nErrId;
			ELSE
				_iCurrentErrorCountWRS := 0;
			END_IF
		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 := 30;
			IF _fbWritePowerCommand.bError THEN
				_iCurrentErrorCountWPC := _iCurrentErrorCountWPC + 1;
				_iErrorCountWPC := _iErrorCountWPC + 1;
				_iErrorIDWPC := _fbWritePowerCommand.nErrId;
			ELSE
				_iCurrentErrorCountWPC := 0;
			END_IF
		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 := 40;
			IF _fbReadCurrentState.bError THEN
				_iCurrentErrorCountRCS := _iCurrentErrorCountRCS + 1;
				_iErrorCountRCS := _iErrorCountRCS + 1;
				_iErrorIDRCS := _fbReadCurrentState.nErrId;
			ELSE
				_iCurrentErrorCountRCS := 0;
			END_IF
		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 := 50;
			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
		
	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 := 60;
			// 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
		
	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 := 0;
			// 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
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 AND xReleasePower THEN
	_xErrorInverter := TRUE;
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 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]]></ST>
      </Implementation>
    </Action>
    <Action Name="HandleHeartbeat" Id="{eeb5f65a-fd91-4c22-ab2e-3080c24e87fb}">
      <Implementation>
        <ST><![CDATA[// Self resetting watchdog timer
_tonWatchdogResetTimer(IN := TRUE);

// Timeout should be less than timer interval
_fbWriteHearbeatRegister(
	sIPAddr:= sInverterIPAddr, 
	nTCPPort:= 502, 
	nUnitID:= 16#01, 
	nMBAddr:= WATCHDOG_REGISTER, 
	nValue:= _uiWatchdogTimeoutSeconds, 
	bExecute:= _tonWatchdogResetTimer.Q AND (NOT _fbWriteHearbeatRegister.bBusy), 
	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
	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

// set fault flag when error active
IF NOT xHeartbeatOk THEN
	_xHeartBeatNOK := TRUE;
END_IF

// handle 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

// Reset timer
IF _tonWatchdogResetTimer.Q THEN
	_tonWatchdogResetTimer(IN := FALSE);
END_IF]]></ST>
      </Implementation>
    </Action>
    <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);
_fbCyclicDataAlarm.ipArguments.Clear().AddString(_sName);
_fbHeartBeatAlarm.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
		_xBatteryLimitsSet := FALSE;
		_xErrorSetBatteryLimits := FALSE;
		_xStartupBusy := TRUE;
		_iStateStartup := 10;
		
	10: // Read scaling factors
		_fbReadRegisters(
			sIPAddr:= sInverterIPAddr, 
			nTCPPort:= 502, 
			nUnitID:= 16#01, // 16#FF for Modbus TCP
			nQuantity:= 2, 
			nMBAddr:= BATTERY_LIMIT_SF_START, 
			cbLength:= SIZEOF(_arBattScalingFactors), 
			pDestAddr:= ADR(_arBattScalingFactors), 
			bExecute:= TRUE, 
			tTimeout:= T#5S, 
			bBusy=> , 
			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]]></ST>
      </Implementation>
    </Action>
  </POU>
</TcPlcObject>