2

I'm still very new to OOP and TwinCat so please bear with me. I'm currently developing the software for a small machine that will be used combined with the TF2000 HMI. Because the Event Grid takes away a lot of work I want to set up the TC3 EventLogger. I understand how you create alarms etc. and I can display them in the HMI Event Grid.

It works great with single events, but now I want to take it further and add the errors of every function block. For example FB_TemperatureController can report overheat, FB_Motor can report a stop error and so on.

How do I setup the event logger so I can send the same error from every instantiated FB?

I thought of creating a FB_FaultHandler:

FUNCTION_BLOCK FB_FaultHandler
VAR
    fbEventLogger       : FB_TcEventLogger;
    aMessages           : ARRAY [0..100] OF Fb_TcMessage;
    aAlarms             : ARRAY [0..100] OF Fb_TcAlarm;
END_VAR

METHOD init
VAR_INPUT
END_VAR

aAlarms[0].CreateEx(Tc_Events.AlarmEvents.Error_Overtemp, TRUE, 0);
aAlarms[1].CreateEx(Error2, TRUE, 0);
aAlarms[2].CreateEx(Error3, TRUE, 0);
aAlarms[3].CreateEx(Error4, TRUE, 0);
aAlarms[4].CreateEx(, TRUE, 0);
aAlarms[5].CreateEx(, TRUE, 0);
aAlarms[6].CreateEx(, TRUE, 0);

There's a few things I don't like about this tho:

  1. The source of the event would always be FB_FaultHandler but I want it to display the concerning FB
  2. I'm not sure how to raise an alarm from different instances twice. For example: fbTemp1 and fbTemp 2 are both instances of FB_TemperatureController. Now if I create a "SetFault" Method in the FB_FaultHandler, which raises and confirms an alarm it would raise the same error twice without me knowing the source.
METHOD setError
VAR_INPUT
  nErrorId: INT;
  bErrorActive: BOOL;
END_VAR
  1. I'd prefer if the array would be set up with the id of an event. So "Error_Overtemp" has id 96 and the FB_FaultHandler will put it in aAlarms[96]

I haven't really found any real samples about this anywhere. I watched the Webinar but it honestly is described very poorly. I'd be thankful for any help, input or examples of a good event logger.

faultvault
  • 23
  • 3

3 Answers3

1

Off the top of my head with some help of the internet. Don't have a system for compiling at hand. But you could do something like this code below.

Essential there is a baseclass for logging (your FB_FaultHandler). From this all classes with logging functionality can be derived from. It uses reflection for the instance path. I never used reflection in twincat as I usually solve this kind of problem a little different. But it is the most generic solution I guess.
An example for the final class is FB_MyClassesWithLoggingFunctionality.

There is also a small stub for the initialization code in MAIN. You have to initialize and trigger the setting of Info Class FB_SourceInfo. It sets the instance information in the FB_SourceInfo. That is a class that you can handover to the CreateEx Method and should provide the source of the instance information.

In the below example of MAIN the source info would be like -project-.MAIN.fbClass

// -------------- Base class for all classes with logging
{attribute 'reflection'}
FUNCTION_BLOCK FB_FaultHandler
VAR
    {attribute 'instance-path'}
    {attribute 'noinit'}
    sErrorSource : STRING; // make sure that this variable is large enough to hold the path

    fbFaultInfo : FB_SourceInfo;

    fbEventLogger       : FB_TcEventLogger;
    aMessages           : ARRAY [0..100] OF Fb_TcMessage;
    aAlarms             : ARRAY [0..100] OF Fb_TcAlarm;
END_VAR


METHOD InitInfo
fbFaultInfo.InitInfo(sErrorSource);
// example: use faultInfo
aAlarms[0].CreateEx(Tc_Events.AlarmEvents.Error_Overtemp, TRUE, fbFaultInfo);
END_METHOD

METHOD logError
VAR_INPUT
    errorId : INT;
END_VAR
END_METHOD
   // Raise error
END_FUNCTION_BLOCK


// ------------ FB for source Info
FUNCTION_BLOCK FB_SourceInfo IMPLEMENTS I_TcSourceInfo 
VAR
    sSourceInfo : STRING
METHOD InitInfo
VAR_INPUT
    source : STRING
END_VAR
sSourceInfo := source;

END_METHOD


END_FUNCTION_BLOCK

// ------------------  Final class that has logging functions
FUNCTION_BLOCK FB_MyClassesWithLoggingFunctionality EXTENDS FB_FaultHandler

METHOD DoSomething
VAR_INPUT

END_VAR

IF bError then
    logError(id);
END_IF
END_METHOD


// Usage
PROGRAM MAIN
VAR
    fbClass : FB_MyClassesWithLoggingFunctionality;
    bInit : BOOL;
END_VAR
IF NOT bInit THEN
    fbClass.InitInfo();
    bInit := FALSE;
END_IF

END_PROGRAM
Uwe Hafner
  • 4,889
  • 2
  • 27
  • 44
  • Hello, thank you for your detailed answer. I'm a bit confused where you would raise the alarm in your example. I only see you creating it with `createEx();`. Additionally I would have to know the array index in any `logError();` Method right? – faultvault Jul 12 '21 at 06:51
  • You are right. Was a quick-hack yesterday. logError is the function you would use to raise the error. I will update it with pseudocode. – Uwe Hafner Jul 12 '21 at 10:27
  • I'm still a bit confused on how you differentiate between errors. Let's say I have two instances of `FB_Motor`. FB_Motor calls the `.InitInfo()` and creates an alarm in `aAlarms[0]` But now I have the problem that both instances would create an Alarm on the same array position, even tho they can be raised at different times. Would I need a for loop, that basically just jumps to the next array index? – faultvault Jul 12 '21 at 11:51
  • 1
    If an error has the same cause it is the same error. What you want to differentiate is if Motor1 or Motor2 is in error condition. That info is in the fbFaultInfo ErrorSource. It tells where the error originated from. – Uwe Hafner Jul 12 '21 at 15:58
  • Excuse my very late answer but I was busy with a different project in the meantime. Thanks for the solution you provided. Would you mind quickly explaining how you usually solve logging? It doesn't seem like extending a baseclass for logging is optimal as that would not allow me to extend any other classes. – faultvault Apr 06 '22 at 12:21
0

I recommend looking at the documentation for attribute ‘instance-path’. This should tell you which instance of a function block is the source (Motor1 or Motor2). You will find source code example there.

https://infosys.beckhoff.com/content/1033/tc3_plc_intro/3108100107.html?id=5092968876405062789

0

I've struggled with this same question myself. The following is how I've handled it with some success.

All FB_TcAlarms have to be defined in the Type System; as far as I know, there's no way around this. But once you create that TcEventClass with various specific Events, you can instantiate them as many times as necessary.

Also, when I have "Raised" FB_TcAlarms, I didn't need to specify the FB_TcEventLogger. The only time I instantiate and act on the FB_TcEventLogger is when I want to use methods/properties of that object; e.g. .ClearLoggedEvents, .ExportToCsv

What I have done is to make my own FB_Alarm that has an FB_TcAlarm object inside it. I pass into that FB the TcEventEntry definition (as defined in the Type System) that I want the FB_TcAlarm to use. Then I just have a state machine that walks the FB_TcAlarm through its states when Raised, Confirmed, and Cleared. Confirmation takes place for us via the TE2000 EventGrid control.

In each of my functional process units (Motor, X-Y Gantry, etc), I embed my FB_Alarm and pass the proper inputs for the type of Alarm I want to Raise. I also have an sIdentifier string that is automatically put inside the "Display Text" of the Event based on the {n} argument feature that the EventLogger Manual talks about.

So if I have an alarm for a motor inside my X-Y Gantry, both the path for the alarm and the display text that I pushed in will uniquely identify where the Alarm came from.

I'm sure this reads like hot trash, but I do feel your pain on the ambiguity of Beckhoff's manuals.

Adam
  • 21
  • 2