Simple ArEventLog C++ program

Hi, I am trying to create a simple ArEventLog program, but I do not seem to get the logs to show in the SDM. Does anyone know what could be the issue? Below is my code:

#ifdef _DEFAULT_INCLUDES
#include <AsDefault.h>
#endif

void _INIT HelloWorldINIT(void)
{
ArEventLogCreate_typ logSettings;

logSettings.Name[0] = 'a';
logSettings.Size = 65535;
logSettings.Persistence = arEVENTLOG_PERSISTENCE_PERSIST;
logSettings.Info = 0;

ArEventLogCreate(&logSettings);


ArEventLogWrite_typ log;
log.Execute = true;
log.Ident = 2345;
log.EventID = 234;
log.OriginRecordID = 23;
log.AddDataSize = 0;
log.AddDataFormat = 0;
log.AddData = 0;
log.ObjectID[0] = 'b';
log.TimeStamp = 0;

ArEventLogWrite(&log);

}
void _CYCLIC HelloWorldCYCLIC(void)
{
std::cout << “Hello World!” << std::endl; /* Written to the outputBuffer PV */

ArEventLogWrite_typ log;
log.Execute = true;
log.Ident = 2345;
log.EventID = 234;
log.OriginRecordID = 23;
log.AddDataSize = 10;
log.AddDataFormat = 0;
log.AddData = 65535;
log.ObjectID[0] = 'b';
log.TimeStamp = 0;

ArEventLogWrite(&log);

}
void _EXIT HelloWorldEXIT(void)
{
/* ANSI C++ program exit function */

}

Hello,

The Functionblock “ArEventLogCreate” is marked as an Asynchronously Function block. It must be called until its Busy is FALSE and the Done/Error is TRUE.
It looks like you call it only once in an Init-Section of a Task.
Please add a loop or move it to the cyclic section.

In Addition i am not sure if the Execute Input of “ArEventLogCreate” is set to TRUE.

There is a working sample in ST available in the Automation Studio.


As you use C-Language you must rewrite it, but i think you should be able to read the code.

Greetings
Michael

1 Like

Thanks for your inputs. I tried the code below, not sure if it is the correct method. The functions in C are different from the ones used in the Structured Text (st)? :

/* HelloWorld.cpp */
#include <bur/plctypes.h>
#include
#include <ArEventLog.h>

unsigned long bur_heap_size = 0xFFFF;
#ifdef _DEFAULT_INCLUDES
#include <AsDefault.h>
#endif

void _INIT HelloWorldINIT(void)
{

}
void _CYCLIC HelloWorldCYCLIC(void)
{
ArEventLogCreate_typ logSettings;

logSettings.Name[0] = 'a';
logSettings.Size = 65535;
logSettings.Persistence = arEVENTLOG_PERSISTENCE_PERSIST;
logSettings.Info = 0;
logSettings.Execute = 1;
ArEventLogCreate(&logSettings);


if (logSettings.Busy==0)
{
	ArEventLogWrite_typ log;
	log.Execute = 1;
	log.Ident = 2345;
	log.EventID = 234;
	log.OriginRecordID = 23;
	log.AddDataSize = 10;
	log.AddDataFormat = 0;
	log.AddData = 65535;
	log.ObjectID[0] = 'b';
	log.TimeStamp = 0;

	ArEventLogWrite(&log);		
}

}
void _EXIT HelloWorldEXIT(void)
{
/* ANSI C++ program exit function */

}

The functions in C are different from the ones used in the Structured Text (st)?

The Functions of the ArEventLog Library are the same. It is only the Program Syntax which is different between C and ST.


How is the outcome of the Change. Do you get now a Done/Error Status of the Function Block Instance “logSettings”?

If you still have issues in not seeing it in the SDM, you might also have a look into the Automation Studio Logbook, if it is visible there.

Greetings Michael

.Done and .Error are different members? Do we check for the .Done to be 1 and .Error to be 0?

Hi,

The interface is visible in the corresponding help page here - including .Done and .Error and also StatusID which is relevant in case of an error as well.

The various function blocks are language independent and therefore the interface is equal no matter if you use C, C++, ST or any of the other available languages.

If you declared the FUB instance variables in the *var File you can also insert them into the variable watch and have a look at the outputs and the behavior generally.
Having the outputs of the FUBs visible should be more useful to you at the moment than the actual log line.

Generally speaking, when I am not familiar with a specific LIB or Function, I try only setting variables in code that I can’t reasonably set in the watch (mostly pointers), the rest I set via the watch so I can see the outputs reacting live and I could also reset the inputs like “Execute” which is edge-triggered.

After that you can automate it and have the code do everything for you.

1 Like

I did now a whole sample:

0. Add Library ArEventLog to the Project

1. Create Task from Toolbox

2. Declare used Variables in IEC Declaration
Variables in this declaration can be used in Watch Window of Automation Studio
image

VAR
	Step : USINT;
	ArEventLogCreate_0 : ArEventLogCreate;
	ArEventLogGetIdent_0 : ArEventLogGetIdent;
	ArEventLogWrite_0 : ArEventLogWrite;
	Ident : UDINT;
END_VAR

3. Add Cyclic Code
image

#include <bur/plctypes.h>

#ifdef _DEFAULT_INCLUDES
#include <AsDefault.h>
#endif

// amount of memory to be allocated for heap storage must be specified for every ANSI C++ program with the bur_heap_size variable
unsigned long bur_heap_size = 0xFFFF; 

void _INIT ProgramInit(void)
{
	// Insert code here 
	
}

void _CYCLIC ProgramCyclic(void)
{
	
	switch (Step)
	{
		case 0: 
			// Add Start Condition if nessesary 
			Step = 5;
		
			break;
		
		case 5:
			ArEventLogGetIdent_0.Name[0] = 'a';
			ArEventLogGetIdent_0.Execute = false; 
	
			ArEventLogGetIdent(&ArEventLogGetIdent_0);
		
			Step = 10; 
		
			break;
		
		case 10:
	
			ArEventLogGetIdent_0.Execute = true; 
			ArEventLogGetIdent(&ArEventLogGetIdent_0);
			
			if (ArEventLogGetIdent_0.Done) 
			{
				Ident = ArEventLogGetIdent_0.Ident; 
				ArEventLogGetIdent_0.Execute = false; 
				Step = 30;		// Write 
			}			
			else if (ArEventLogGetIdent_0.Error) 
			{
				if (ArEventLogGetIdent_0.StatusID == arEVENTLOG_ERR_LOGBOOK_NOT_FOUND)
				{
					Step = 20; 	// create
					
					ArEventLogCreate_0.Name[0] 		= 'a'; 
					ArEventLogCreate_0.Size 		= 65535;
					ArEventLogCreate_0.Persistence 	= arEVENTLOG_PERSISTENCE_PERSIST;
					ArEventLogCreate_0.Info 		= 0;
					ArEventLogCreate_0.Execute 		= false;
					ArEventLogCreate(&ArEventLogCreate_0);
					
				}
				else
				{
					Step = 255; // error 	
				}
			}
			
			break;
		
		case 20:
			
			
			ArEventLogCreate_0.Execute = true; 
			ArEventLogCreate(&ArEventLogCreate_0);
			
			if (ArEventLogCreate_0.Done) 
			{
				Ident = ArEventLogCreate_0.Ident;
				ArEventLogCreate_0.Execute = false; 
				Step = 30;		// Write 
			}
			else if (ArEventLogCreate_0.Error) 
			{
				Step = 255; // error 	
			}
			
			break;
			
		case 30:
			
			ArEventLogWrite_0.Ident				= Ident; 
			ArEventLogWrite_0.EventID			= 536870912;
			ArEventLogWrite_0.OriginRecordID	= 0; 
			ArEventLogWrite_0.AddDataSize		= 0; 	
			ArEventLogWrite_0.AddDataFormat		= 1; 
			ArEventLogWrite_0.AddData			= 0;
			ArEventLogWrite_0.ObjectID[0]		        = '1';
			ArEventLogWrite_0.TimeStamp			= 0; 	
			ArEventLogWrite_0.Execute			        = true; 
			ArEventLogWrite(&ArEventLogWrite_0);	// synchron fub call 
		
			ArEventLogWrite_0.Execute			       = false; 
			ArEventLogWrite(&ArEventLogWrite_0);	// synchron fub call 
		
			Step = 40;
		
			break;
		
		case 40:
		
			break;
		
	}
}

void _EXIT ProgramExit(void)
{
	// Insert code here 

}

4. Build Transfer
image

5. Open Watch
If the Step is at 40 the code was succesfull , step 255 is error
image

6. Open Logger in Automation Studio

7. Open SDM


To create correct EventIDs you can use this Calculator in the AS-Help. There are some roules which must be considered. They are also documented on this Help page.
EventID Calculator

Greetings
Michael

4 Likes

Thanks a lot for your help! I can see the logger and log message currently in AS and SDM. Just a note for others if trying is we also need to add the ArEventLog library to the project also.

Just to understand the code, why do we need to make another call below for Execute false in the event log write?

		ArEventLogWrite_0.Execute			        = true; 
		ArEventLogWrite(&ArEventLogWrite_0);	// synchron fub call 
	
		ArEventLogWrite_0.Execute			       = false; 
		ArEventLogWrite(&ArEventLogWrite_0);	// synchron fub call

Also, is there a way to populate the strings for the logs? Is it under AddData?

You don’t have to do it that way (referring to running the fb twice).

In fact, if you read the TM231 B&R Coding Guidelines, it has a “Rule C305: Only call function block instances once per program cycle”.

A common way to do it would be to run the second function block call in step 40 instead (or whatever “idle” state you may go back to while waiting for the impetus to write another log entry), so it becomes:

case 40:
	ArEventLogWrite_0.Execute = false;
	ArEventLogWrite(&ArEventLogWrite_0);
		
	break;

But by then, since you are running the very same line of code in every state (except 0 right now), it is common to move the function block call outside the state machine, usually right below it:

...
		case 40:
			ArEventLogWrite_0.Execute = false;
			
			break;
	}
}

ArEventLogWrite(&ArEventLogWrite_0);

This means that a) you have less repetition of code, and b) there is less of a risk that you forget to call the function block in a particular state.

But to be honest it’s a matter of taste as far as I’m concerned. The illustrated way of doing it works perfectly fine. It avoids running the function block at all when the service is not being executed, and absolutely everything that happens can be gleaned from the code of a given state.

The important thing is probably that you understand the pros and cons and the pitfalls of various ways, and that you try to be consistent in how you do things.

1 Like

For each log we log to ArEventLog, does it have to go through all these steps in the cyclic function? Are the step numbers arbitrary?

You do not have to go through all the steps for every single log entry.

You only need to make a successful call to ArEventLogWrite every time you want to add an entry (i.e. until Done is true).

ArEventLogGetIdent is for getting a handle to an existing log, such as a above. The function block outputs an Ident value which you use as an input in the ArEventLogWrite to write to the chosen log. You only run ArEventLogGetIdent to get this Ident value once, usually when you boot up.

If you don’t find the log, because it hasn’t been created yet (first boot), as given by the condition ArEventLogGetIdent_0.StatusID == arEVENTLOG_ERR_LOGBOOK_NOT_FOUND, you would run ArEventLogCreate, just as in the example given above.

The step numbers are arbitrary to the example, you can choose any step numbers.

(A recommendation is to consider using enums instead if possible, since enums are more human-readable than numbers, and it also safeguards against errors if you refactor your code.)

1. Add Library
Thanks for the hint with the need of the Library in the Project. This was just as basic that i forgot to mention it. I added it by editing.

2. Add Data
Yes you can add Detail Information via the AddData-Part. There are two Fromats you can use.

  • arEVENTLOG_ADDFORMAT_TEXT
    Just Add a ASCII String via a Pointer
  • arEVENTLOG_ADDFORMAT_CODED
    Use the AddData Functions to build a Data Set and attatch it
    Work with AddData
    Work with Texts

3. About the double call of the Function Block ArEventLogWrite

It is true that there is a Coding Guideline. But for me function is before Guideline.

And at least i think a to hard use of Guidelines lead to this wired situation… The Functionblock is in reality a more or less synchron function. But due to some API rouls it has a Functionblock Design and an Execute Input which requires a PositiveEdge to start the Operation. Which is basicaly a Design for an asynchronous Function Block.

Due to this sircumstance you will be forced to call it twice in a Cycle if you want to use it Synchronously.

To come over this wird looking code i created a wrapper for my own Programms were i can call it as function.

(ST-Code as i had no time to rewrite it)

FUNCTION TLogWriteSDDD
	
	TLogWriteSDDD := 1; 

	IF Ident <> 0 THEN 
		pSharedData ACCESS Ident;
		
		IF pSharedData.StatusCore = tlogsdSTATECORE_ACTIVE THEN 

			// Generate Event ID 
			ResultEventID 			:= ArEventLogMakeEventID(DINT_TO_USINT(Severity),Facility,Code);

			// Generate Additional Data 
			ResultAddDataInit 		:= ArEventLogAddDataInit(Buffer:=ADR(AddData),BufferSize:=SIZEOF(AddData),Format:=arEVENTLOG_ADDFORMAT_CODED);
			ResultAddDataString 	:= ArEventLogAddDataString(Buffer:=ADR(AddData),BufferSize:=SIZEOF(AddData),Value:=ADR(Arg1STRING));
			ResultAddDataDint 		:= ArEventLogAddDataDint(Buffer:=ADR(AddData),BufferSize:=SIZEOF(AddData),Value:=Arg2DINT);
			ResultAddDataDint 		:= ArEventLogAddDataDint(Buffer:=ADR(AddData),BufferSize:=SIZEOF(AddData),Value:=Arg3DINT);
			ResultAddDataDint 		:= ArEventLogAddDataDint(Buffer:=ADR(AddData),BufferSize:=SIZEOF(AddData),Value:=Arg4DINT);

			// Write Event 
			ArEventLogWrite_0.Ident 			:= pSharedData.LogIdent;
			ArEventLogWrite_0.EventID 			:= ResultEventID;
			ArEventLogWrite_0.TimeStamp 		:= 0; (*System timestamp.*)
			ArEventLogWrite_0.ObjectID 			:= EnteredBy;
			ArEventLogWrite_0.OriginRecordID 	:= OriginID;
			ArEventLogWrite_0.AddDataFormat 	:= arEVENTLOG_ADDFORMAT_CODED;
			ArEventLogWrite_0.AddDataSize 		:= SIZEOF(AddData);
			ArEventLogWrite_0.AddData 			:= ADR(AddData);
	
			ArEventLogWrite_0(Execute := TRUE);
	
			IF ArEventLogWrite_0.Done THEN 
				CurrentID 		:= ArEventLogWrite_0.RecordID;
				TLogWriteSDDD 	:= ERR_OK;
			END_IF
	
			ArEventLogWrite_0(Execute := FALSE);
		END_IF
	END_IF

	
END_FUNCTION

4. What do you have to call to make an entry
Thanks @c329837 for the hints.

After having the Ident of the Logbook you can use only write-function to set an Entry.

If you like to enter 2 Log lines in a cycle you have to call the Functionblock much more often in a cycle. Number of Entrys per Cycle * 2