Changed Kaco communication statemachine

- Added small delay after each modbus/tcp read
- Only write requested state when requested state has changed
This commit is contained in:
Markus.Neukirch
2025-09-09 12:25:29 +02:00
parent 755da644bc
commit d7cb895e29
2 changed files with 120 additions and 69 deletions

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4026.12">
<DUT Name="E_KACO_PCU_REQUESTED_STATE" Id="{d26b8bd2-4451-4a56-a3ba-471d8a90e01d}">
<Declaration><![CDATA[{attribute 'qualified_only'}
{attribute 'strict'}
{attribute 'to_string'}
TYPE E_KACO_PCU_REQUESTED_STATE :
(
UNDEFINED := 0,
OFF := 1,
STANDBY := 8,
GRID_PRE_CONNECTED := 10,
GRID_CONNECTED := 11
);
END_TYPE
]]></Declaration>
</DUT>
</TcPlcObject>

View File

@@ -49,7 +49,8 @@ VAR
_rPowerInternal : REAL := 0;
// Enum for requested state
_eRequestedState : (OFF := 1, STANDBY := 8, GRID_PRE_CONNECTED := 10, GRID_CONNECTED := 11) := OFF;
_eRequestedState : E_KACO_PCU_REQUESTED_STATE := E_KACO_PCU_REQUESTED_STATE.OFF;
_eLastRequestedState : E_KACO_PCU_REQUESTED_STATE := E_KACO_PCU_REQUESTED_STATE.UNDEFINED;
// Watchdog timeout in seconds
_uiWatchdogTimeoutSeconds : UINT := 10;
@@ -85,20 +86,13 @@ VAR
_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;
_timPollingDelay : TIME := T#200MS;
// 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);
// watchdog write timeout
_tTimeoutWriteWatchdogRegister : TIME := T#5S;
// Inverter alarm
@@ -199,10 +193,12 @@ VAR CONSTANT
// 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)
@@ -248,7 +244,6 @@ IF (rPower < -rMaxBattPower) THEN
_rPowerInternal := -rMaxBattPower;
END_IF
HandleHeartbeat();
HandleCyclicData();
CASE _iState OF
@@ -256,7 +251,7 @@ CASE _iState OF
_fbTONSetBatteryLimits(IN := TRUE);
IF _fbTONSetBatteryLimits.Q THEN
_fbTONSetBatteryLimits(IN := FALSE);
_eRequestedState := OFF;
_eRequestedState := E_KACO_PCU_REQUESTED_STATE.OFF;
_iStateStartup := 0;
_iState := 10;
END_IF
@@ -348,7 +343,7 @@ CASE _iState OF
40: // Idle state, wait for enable
// If enable and INTLK Ok
IF xEnable THEN
_eRequestedState := GRID_CONNECTED;
_eRequestedState := E_KACO_PCU_REQUESTED_STATE.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!)
@@ -360,14 +355,14 @@ CASE _iState OF
_iWSetPct := 0;
END_IF
ELSE
_eRequestedState := OFF;
_eRequestedState := E_KACO_PCU_REQUESTED_STATE.OFF;
_iWSetPct := 0;
END_IF
// Comm error or Watchdog error occured
IF _xErrorCyclicData OR (NOT xHeartbeatOk) THEN
_iWSetPct := 0;
_eRequestedState := OFF;
_eRequestedState := E_KACO_PCU_REQUESTED_STATE.OFF;
_iState := 1000;
END_IF
@@ -384,7 +379,7 @@ CASE _iState OF
1001: // Error state, wait for reset
IF xReset AND (NOT xEnable) AND (NOT _xErrorInverter) AND (NOT _xErrorCyclicData) AND xHeartbeatOk THEN
_eRequestedState := OFF;
_eRequestedState := E_KACO_PCU_REQUESTED_STATE.OFF;
xError := FALSE;
_xFaultInverter := FALSE;
_xErrorCyclicDataLedge := FALSE;
@@ -463,14 +458,16 @@ _fbCyclicDataAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
_iErrorIDRACV := 0;
END_IF
// Fetch cyclic data with polling timer
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
CASE _iStateCyclicData OF
0: // init state
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iStateCyclicData := 10;
IF _eRequestedState <> _eLastRequestedState THEN
_iStateCyclicData := 10;
ELSE
_iStateCyclicData := 20;
END_IF
END_IF
10: // Write requested state
@@ -488,15 +485,23 @@ CASE _iStateCyclicData OF
IF (NOT _fbWriteRequestedState.bBusy) THEN
_fbWriteRequestedState(bExecute := FALSE);
_iStateCyclicData := 20;
_iStateCyclicData := 11;
IF _fbWriteRequestedState.bError THEN
_iCurrentErrorCountWRS := _iCurrentErrorCountWRS + 1;
_iErrorCountWRS := _iErrorCountWRS + 1;
_iErrorIDWRS := _fbWriteRequestedState.nErrId;
ELSE
_iCurrentErrorCountWRS := 0;
_eLastRequestedState := _eRequestedState;
END_IF
END_IF
11: // wait for next state
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iStateCyclicData := 20;
END_IF
20: // Write current power command
_fbWritePowerCommand(
@@ -515,7 +520,7 @@ CASE _iStateCyclicData OF
IF (NOT _fbWritePowerCommand.bBusy) THEN
_fbWritePowerCommand(bExecute := FALSE);
_iStateCyclicData := 30;
_iStateCyclicData := 21;
IF _fbWritePowerCommand.bError THEN
_iCurrentErrorCountWPC := _iCurrentErrorCountWPC + 1;
_iErrorCountWPC := _iErrorCountWPC + 1;
@@ -525,6 +530,13 @@ CASE _iStateCyclicData OF
END_IF
END_IF
21: // wait for next state
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iStateCyclicData := 30;
END_IF
30: // Read current state
_fbReadCurrentState(
sIPAddr:= sInverterIPAddr,
@@ -543,7 +555,7 @@ CASE _iStateCyclicData OF
IF (NOT _fbReadCurrentState.bBusy) THEN
_fbReadCurrentState(bExecute := FALSE);
_iStateCyclicData := 40;
_iStateCyclicData := 31;
IF _fbReadCurrentState.bError THEN
_iCurrentErrorCountRCS := _iCurrentErrorCountRCS + 1;
_iErrorCountRCS := _iErrorCountRCS + 1;
@@ -552,6 +564,13 @@ CASE _iStateCyclicData OF
_iCurrentErrorCountRCS := 0;
END_IF
END_IF
31: // wait for next state
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iStateCyclicData := 40;
END_IF
40: // Read current pcu status
_fbReadPCUState(
@@ -571,7 +590,7 @@ CASE _iStateCyclicData OF
IF (NOT _fbReadPCUState.bBusy) THEN
_fbReadPCUState(bExecute := FALSE);
_iStateCyclicData := 50;
_iStateCyclicData := 41;
IF _fbReadPCUState.bError THEN
_iCurrentErrorCountRPCUS := _iCurrentErrorCountRPCUS + 1;
_iErrorCountRPCUS := _iErrorCountRPCUS + 1;
@@ -584,6 +603,13 @@ CASE _iStateCyclicData OF
END_IF
END_IF
41: // wait for next state
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iStateCyclicData := 50;
END_IF
50: // Read current dc values
_fbReadDCValues(
sIPAddr:= sInverterIPAddr,
@@ -603,7 +629,7 @@ CASE _iStateCyclicData OF
// Check if reading modbus register is done
IF (NOT _fbReadDCValues.bBusy) THEN
_fbReadDCValues(bExecute := FALSE);
_iStateCyclicData := 60;
_iStateCyclicData := 51;
// 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])));
@@ -620,6 +646,13 @@ CASE _iStateCyclicData OF
END_IF
END_IF
51: // wait for next state
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iStateCyclicData := 60;
END_IF
60: // Read current ac values
_fbReadACValues(
sIPAddr:= sInverterIPAddr,
@@ -639,7 +672,7 @@ CASE _iStateCyclicData OF
// Check if reading mudbus register is done
IF (NOT _fbReadACValues.bBusy) THEN
_fbReadACValues(bExecute := FALSE);
_iStateCyclicData := 0;
_iStateCyclicData := 61;
// 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])));
@@ -667,8 +700,49 @@ CASE _iStateCyclicData OF
_iErrorIDRACV := _fbReadACValues.nErrId;
END_IF
END_IF
61: // wait for next state
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iStateCyclicData := 70;
END_IF
70:
// Timeout should be less than timer interval
_fbWriteHearbeatRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#01,
nMBAddr:= WATCHDOG_REGISTER,
nValue:= _uiWatchdogTimeoutSeconds,
bExecute:= TRUE,
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
_fbWriteHearbeatRegister(bExecute := FALSE);
_iStateCyclicData := 0;
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
END_CASE
// evaluate inverter state
IF _eCurrentState = E_KACO_CURRENT_STATE.GRID_CONNECTED OR _eCurrentState = E_KACO_CURRENT_STATE.THROTTLED THEN
xActive := TRUE;
@@ -710,7 +784,7 @@ IF _xErrorCyclicData THEN
_xErrorCyclicDataLedge := TRUE;
END_IF
// handle alarm
// handle cyclic data alarm
IF _xErrorCyclicData AND NOT _fbCyclicDataAlarm.bRaised THEN
_fbCyclicDataAlarm.Raise();
END_IF
@@ -721,42 +795,6 @@ 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
@@ -764,7 +802,7 @@ IF NOT xHeartbeatOk THEN
_xHeartBeatNOK := TRUE;
END_IF
// handle alarm
// handle heartbeat alarm
IF NOT xHeartbeatOk AND NOT _fbHeartBeatAlarm.bRaised THEN
_fbHeartBeatAlarm.Raise();
END_IF
@@ -775,11 +813,6 @@ 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>