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
1011 lines
29 KiB
XML
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> |