From d7cb895e296eb4de678c223962e62df7f1724b3a Mon Sep 17 00:00:00 2001 From: "Markus.Neukirch" Date: Tue, 9 Sep 2025 12:25:29 +0200 Subject: [PATCH] Changed Kaco communication statemachine - Added small delay after each modbus/tcp read - Only write requested state when requested state has changed --- .../Kaco/E_KACO_PCU_REQUESTED_STATE.TcDUT | 18 ++ .../Sunspec/Kaco/FB_PowerSupplyKaco.TcPOU | 171 +++++++++++------- 2 files changed, 120 insertions(+), 69 deletions(-) create mode 100644 PLC/POUs/Sunspec/Kaco/E_KACO_PCU_REQUESTED_STATE.TcDUT diff --git a/PLC/POUs/Sunspec/Kaco/E_KACO_PCU_REQUESTED_STATE.TcDUT b/PLC/POUs/Sunspec/Kaco/E_KACO_PCU_REQUESTED_STATE.TcDUT new file mode 100644 index 0000000..acd2dd6 --- /dev/null +++ b/PLC/POUs/Sunspec/Kaco/E_KACO_PCU_REQUESTED_STATE.TcDUT @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/PLC/POUs/Sunspec/Kaco/FB_PowerSupplyKaco.TcPOU b/PLC/POUs/Sunspec/Kaco/FB_PowerSupplyKaco.TcPOU index 6cda1a9..9db725d 100644 --- a/PLC/POUs/Sunspec/Kaco/FB_PowerSupplyKaco.TcPOU +++ b/PLC/POUs/Sunspec/Kaco/FB_PowerSupplyKaco.TcPOU @@ -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);]]> _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]]> - - - - - , - 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]]>