Files
Uniper_PLC/PLC/POUs/Sunspec/Kaco/FB_PowerSupplyKaco.TcPOU
Markus.Neukirch f0e6143997 IBN changes
added sync units for cabinet temperature, changes in modbus interface to EMS (1.0.4 and 1.0.5), added error counter to modbus communication, lot of changes to kaco (faults, consecutive errors, bms error messages), isolation error ledge, allowed startbalancing when on shutdown, tower light integration
2025-09-05 14:24:37 +02:00

1011 lines
29 KiB
XML

<?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;
// 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#1S;
// 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 cabinet temperature
_iCabTemp : INT;
// 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 := REAL_TO_INT((_rPowerInternal*100) / (_rWMax * EXPT(10,_iWSetPctSF)));
ELSE
_iWSetPct := 0;
END_IF
ELSE
_eRequestedState := OFF;
_iWSetPct := 0;
END_IF
// Comm error or Watchdog error occured
IF _xErrorCyclicData OR (NOT xHeartbeatOk) THEN
_iWSetPct := 0;
_eRequestedState := OFF;
_iState := 1000;
END_IF
// Dont set inverter into off state when an internal error occured
// because this will reset the error message
IF _xErrorInverter 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);
// Write requested state
_fbWriteRequestedState(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#01,
nMBAddr:= REQUESTED_STATE_REGISTER,
nValue:= INT_TO_WORD(_eRequestedState),
bExecute:= _tonPollingTimer.Q AND (NOT _fbWriteRequestedState.bBusy),
tTimeout:= _tTimeoutPolling,
bBusy=> ,
bError=> ,
nErrId=> );
IF (NOT _fbWriteRequestedState.bBusy) THEN
IF _fbWriteRequestedState.bError THEN
_iCurrentErrorCountWRS := _iCurrentErrorCountWRS + 1;
_iErrorCountWRS := _iErrorCountWRS + 1;
_iErrorIDWRS := _fbWriteRequestedState.nErrId;
ELSE
_iCurrentErrorCountWRS := 0;
END_IF
END_IF
// Write current power command
_fbWritePowerCommand(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#01,
nQuantity := 1,
nMBAddr:= W_SET_PCT,
cbLength := SIZEOF(_iWSetPct),
pSrcAddr:= ADR(_iWSetPct),
bExecute:= _tonPollingTimer.Q AND (NOT _fbWritePowerCommand.bBusy),
tTimeout:= _tTimeoutPolling,
bBusy=> ,
bError=> ,
nErrId=> );
IF (NOT _fbWritePowerCommand.bBusy) THEN
IF _fbWritePowerCommand.bError THEN
_iCurrentErrorCountWPC := _iCurrentErrorCountWPC + 1;
_iErrorCountWPC := _iErrorCountWPC + 1;
_iErrorIDWPC := _fbWritePowerCommand.nErrId;
ELSE
_iCurrentErrorCountWPC := 0;
END_IF
END_IF
// Read current state
_fbReadCurrentState(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#01,
nQuantity:= 1,
nMBAddr:= CURRENT_STATE_REGISTER,
cbLength:= SIZEOF(_eCurrentState),
pDestAddr:= ADR(_eCurrentState),
bExecute:= _tonPollingTimer.Q AND (NOT _fbReadCurrentState.bBusy),
tTimeout:= _tTimeoutPolling,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
IF (NOT _fbReadCurrentState.bBusy) THEN
IF _fbReadCurrentState.bError THEN
_iCurrentErrorCountRCS := _iCurrentErrorCountRCS + 1;
_iErrorCountRCS := _iErrorCountRCS + 1;
_iErrorIDRCS := _fbReadCurrentState.nErrId;
ELSE
_iCurrentErrorCountRCS := 0;
END_IF
END_IF
IF _eCurrentState = E_KACO_CURRENT_STATE.GRID_CONNECTED OR _eCurrentState = E_KACO_CURRENT_STATE.THROTTLED THEN
xActive := TRUE;
ELSE
xActive := FALSE;
END_IF
// Read current pcu status
_fbReadPCUState(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#01,
nQuantity:= 2,
nMBAddr:= PCU_STATUS_START_REGISTER,
cbLength:= SIZEOF(_stPCUState),
pDestAddr:= ADR(_stPCUState),
bExecute:= _tonPollingTimer.Q AND (NOT _fbReadPCUState.bBusy),
tTimeout:= _tTimeoutPolling,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
IF (NOT _fbReadPCUState.bBusy) THEN
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
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
// Read current dc values
_fbReadDCValues(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#01, // 16#FF for Modbus TCP
nQuantity:= 6,
nMBAddr:= DC_VALUES_START_REGISTER,
cbLength:= SIZEOF(_awCurrentDCValues),
pDestAddr:= ADR(_awCurrentDCValues),
bExecute:= _tonPollingTimer.Q AND (NOT _fbReadDCValues.bBusy),
tTimeout:= _tTimeoutPolling,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading modbus register is done
IF (NOT _fbReadDCValues.bBusy) THEN
// If there was no error and the converter has no error continue
IF (NOT _fbReadDCValues.bError) THEN
stCurrentValues.rActDCCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentDCValues[0]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[1])));
stCurrentValues.rActDCVoltage := LREAL_TO_REAL(WORD_TO_UINT(_awCurrentDCValues[2]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[3])));
stCurrentValues.rActDCPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentDCValues[4]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[5])));
ELSE
// Dont throw comm error here because this is just
// informational data and not process critical
stCurrentValues.rActDCCurrent := 0.0;
stCurrentValues.rActDCVoltage := 0.0;
stCurrentValues.rActDCPower := 0.0;
_iErrorCountRDCV := _iErrorCountRDCV + 1;
_iErrorIDRDCV := _fbReadDCValues.nErrId;
END_IF
END_IF
// Read current ac values
_fbReadACValues(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#01, // 16#FF for Modbus TCP
nQuantity:= 22,
nMBAddr:= AC_VALUES_START_REGISTER,
cbLength:= SIZEOF(_awCurrentACValues),
pDestAddr:= ADR(_awCurrentACValues),
bExecute:= _tonPollingTimer.Q AND (NOT _fbReadACValues.bBusy),
tTimeout:= _tTimeoutPolling,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF (NOT _fbReadACValues.bBusy) THEN
// If there was no error and the converter has no error continue
IF (NOT _fbReadACValues.bError) THEN
stCurrentValues.rActACCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[0]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4])));
stCurrentValues.rActtACPhaseACurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[1]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4])));
stCurrentValues.rActtACPhaseBCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[2]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4])));
stCurrentValues.rActtACPhaseCCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[3]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4])));
stCurrentValues.rActACPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[12]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[13])));
stCurrentValues.rActACFreq := LREAL_TO_REAL(WORD_TO_UINT(_awCurrentACValues[14]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[15])));
stCurrentValues.rActApparentPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[16]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[17])));
stCurrentValues.rActReactivePower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[18]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[19])));
stCurrentValues.rActPowerFactor := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[20]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[21])));
ELSE
// Dont throw comm error here because this is just
// informational data and not process critical
stCurrentValues.rActACCurrent := 0.0;
stCurrentValues.rActtACPhaseACurrent := 0.0;
stCurrentValues.rActtACPhaseBCurrent := 0.0;
stCurrentValues.rActtACPhaseCCurrent := 0.0;
stCurrentValues.rActACPower := 0.0;
stCurrentValues.rActACFreq := 0.0;
stCurrentValues.rActApparentPower := 0.0;
stCurrentValues.rActReactivePower := 0.0;
stCurrentValues.rActPowerFactor := 0.0;
_iErrorCountRACV := _iErrorCountRACV + 1;
_iErrorIDRACV := _fbReadACValues.nErrId;
END_IF
END_IF
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
// Reset polling timer
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
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);]]></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>