Including extra libraries

In addition to the functions and function-blocks provided already by GEB IDE (IEC 61131-3 standard library), the OEM can pack additional libraries, which can either be globally accesible or can be device-specific. The user then will be able to reference those libraries in his projects (Project -> Properties -> GEB project properties -> Additional libraries) and the available POUs (functions and function blocks) will be available, in textual and graphical editors.

General layout

Each library is a set of files that reside either inside a directory (unpacked format) or packed in a .zip file. Globally available libraries should be placed inside the main lib directories; device specific libraries (preferred) will be inside a lib subdirectory of the respective device directory. For example:

  C:\geb\     => main GEB directory 

  C:\geb\lib\     

  C:\geb\lib\mylib1.zip                 =>  global library "mylib1", packed as zip       

  C:\geb\lib\mylib2\                    =>  global library "mylib2", unpacked         
  C:\geb\lib\mylib2\lib.properties  =>  library properties (mandatory)      
  C:\geb\lib\mylib2\myfunc.st           =>  ST code      
  C:\geb\lib\mylib2\...                 => more files (POUS, C code, C libraries, etc)
  ....      
  C:\geb\devices\DEV1\                  => some device
  C:\geb\devices\DEV1\lib\lib3.zip      => device specific library, packed
  C:\geb\devices\DEV1\lib\lib4\         => device specific library, unpacked
Each library gets its name from its directory or zip filename.

lib.properties format

The lib.properties is mandatory. It's a Java properties file, lines beginning with # are ignored as comments. Currently the only relevant properties are:
  nativeType=20    # 10, 20 see codes below
  charset=UTF-8 # charset of textual source files, optional, defaults to UTF-8 
  libName=My library # user friendly name - optional - should be unique
If lib.properties is absent, type 10 ("normal") is assumed.

Optional funcs.properties file

An optional funcs.properties may be present. It serves to add some informational data. For each POU in the library one can specify a description and a category. These are used when selecting blocks from the Palette in the graphical editor. An example:
# .categories: suggested/prefered order for categories
.categories=Functions,Function Blocks

# .helpurl : if this key exists, a F1 contextual help will be offered 
#  for functions (POUS) and categories
#  The help for functions (POUS) will be browsed at [helpurl]#f[POUNAME] for pous
#  and   [helpurl]#cat[CATNAME] for categories (spaces replaced by _)
.helpurl=http://www.gebautomation.com/help/topic/com.gebautomation.help/html/tech/stdlib.html

# POUS properties follow 
# For POU X , these properties can be specified:
#   X.desc : function description (one line)
#   X.cat  : a category to place the POU inside this library - names are arbitrary

NNF1.desc=Sample function, sums two inputs
NNF1.cat=Functions

NNFB2.desc=Sample FB, CONT starts at 100, increments at the end of each execution, O1 = I1 + CONT ,O2 = I2 + 0.5
NNFB2.cat=Function Blocks

Library types

Currently two types of libraries are supported:

1. Normal external libraries (nativeType=10)
A "normal" library is just a bunch of IEC 61131-3 code in ST language, typically one file for each POU. The effect of including this kind of library is roughly equivalent to including the code in the project. No more files are needed.

2. External libraries with C linking (nativeType=20)
The body of the POUS is coded in C, available in linking time (typically as a static library). This is provided by the OEM (see implementation details below). The simulator is not aware if this, and it relies only the IEC 61131-3 code. Hence, the library should either include also the correct ST body for each POU, or document that the POU does not give meaningful results in simulation.

Implementation details

Some examples, that should roughly correspond to the sample libraries included be included in evaluation IDE.

1. Normal external library (nativeType=10)
In this case it's enough to code the pous in one (or more) ST files which is included in the library folder/zip. For example:

========== func1.st: ==========

(* sums inputs *)
FUNCTION NNF1 : INT 
	 VAR_INPUT i,j : INT ;	END_VAR
	 NNF1 := i + j;
END_FUNCTION

(* 
   Function Block :
   CONT starts at 100, increments by 1 at the end of each execution
   O1 = I1 + CONT 
   O2 = I2 + 0.5
*)
FUNCTION_BLOCK NNFB2
VAR_INPUT I1 : INT;  
I2 : REAL; END_VAR
VAR CONT : INT := 100; END_VAR 
VAR_OUTPUT      O1 : INT; O2 : REAL; END_VAR
 O1 := I1 +CONT;	
 O2 := I2 +0.5; 
 CONT:=CONT+1; 
END_FUNCTION_BLOCK 
2. Native C implementation We also need func1.st the above, but also we need a C implementation, perhaps a wrapper/adapter to an already existing function. For example:

========== func1.h: ==========

#include <geblib.h>
#include <geb.h>

void init_lib1(void); /* library initialization ; name suffix should coincide with library name ; invoked by device specific (main) code */
void destroy_lib1(void);

/* FUNCTION CALL: inputs , in declaration order */
INT_t geb_native_call_f_NF1(INT_t v_i ,INT_t v_j );

/* FB: should not include the EN/ENO check */
/* About the arguments and order: see below */
void geb_native_fb_NFB2( int mode, REAL_t *v_I2, REAL_t *v_O2, INT_t *v_CONT, INT_t *v_I1, INT_t *v_O1, BOOL_t *v_EN, BOOL_t *v_ENO);

========== func1.c: ==========

#include <func1.h>
void init_lib1(void) { }

void destroy_lib1(void) { }

int mysum(int x,int y) {
	return x+y;
}

INT_t geb_native_call_f_NF1(INT_t v_i ,INT_t v_j ) {
	/* NF1 := i + j; */
	return (INT_t)mysum((int)v_i,(int)v_j);
}

void geb_native_fb_NFB2( int mode, REAL_t *v_I2, REAL_t *v_O2, INT_t *v_CONT, INT_t *v_I1, INT_t *v_O1, BOOL_t *v_EN, BOOL_t *v_ENO) {
	if(mode==0) { /* init */
		*v_EN = true;
		*v_ENO = true;
		*v_I1=0;
		*v_O1=0;
		*v_I2=0.0;
		*v_O2=0.0;
		*v_CONT = 100; /* starts at 100! */
	} else if(mode==1) { /* exec  */
		/*
		 O1 := I1 +CONT;
		 O2 := I2 +0.5;
		 CONT:=CONT+1;
		 */
		*v_O1 = *v_I1 + *v_CONT;
		*v_O2 = *v_I2 + 0.5;
		*v_CONT =(*v_CONT+1);
	}
	else if(mode==2) { /* destroy : not used */
	} 
}

This C code would typically be compiled into a static library func1.a and included in the linked libraries when generating the final executable (this can be accomplished either by adding them to the device compiler command line, or by adding them to the compiler).
The init/destroy functions (init_lib1() destroy_lib1()) should be invoked by the device code (eg, inside main.c).
Also, the func1.h header file should be made available in the compilation by including it inside gebud.h

Notice that, in this scenario, the POU body coded in ST will be only executed by the simulator, while debugging in the IDE.

Conventions for arguments in C function callings

Pass-by-value vs pass-by-pointer: Order of arguments: The call for function blocks includes a first argument (mode) which determines the call context: mode=0 for initialization, mode=1 for FB execution. EN/ENO can be read and written, but you don't need to check if EN=TRUE to run the FB body, the caller has already taken care of that.

A recipe to help developers to write the C functions: code only the ST file for the library, as it were a non-native library. Code some Program in the IDE that calls all the functions/FBs in the library. Set the library type to native. In the IDE, compile and generate the C code for your program. Now, the program generated C code will not compile, but it will include for each native call a commented C line that tells you the expected function declaration: search for the comments /* FCALL_NATIVE_C_LIB */ and /* FB_NATIVE_C_LIB */ and copy those lines to your header and C source to start programming.

Restrictions

Working with STRING type in native libraries

Our rules are:

An example, that extends the above func1.h library examples.

======== func1.st: ==========
(* function NF6STR:  joins input strings with a '-' inbetween  *)
FUNCTION NF6STR : STRING
	VAR_INPUT s1,s2 : STRING ; END_VAR
	NF6STR := CONCAT(s1,CONCAT('-',s2));
END_FUNCTION

(* 
   Function block NFB3, with strings
   CONT starts at 400, increments by 1 at the end of each execution
   sx toggles at the end of each execution between YES and NO  (starts at YES)
   O1 := I1 + CONT 
   so := CONCAT(si,INT_TO_STRING(CONT),sx);  
*)
FUNCTION_BLOCK NFB3
VAR_INPUT 
  I1 : INT; 
  si:STRING; 
END_VAR
VAR 
  CONT : INT := 400; 
  sx : STRING := 'YES'; 
END_VAR 
VAR_OUTPUT      
  O1 : INT; 
  so: STRING := '?' ; 
END_VAR
  O1 := I1 + CONT;
  so := CONCAT(CONCAT(si,INT_TO_STRING(CONT)),sx);  
  CONT := CONT+1;
  IF sx = 'YES' THEN
   sx := 'NO';
  ELSE
   sx := 'YES';
  END_IF;
END_FUNCTION_BLOCK   

=== func1.h ===

/* FUNCTION CALL with STRINGS - as with any "non value" datatype, a pointer to the return value is passed as first argument */
void geb_native_call_f_NF6STR(STRING_t_P out, STRING_t_P v_s1 ,STRING_t_P v_s2 );

/* FB with strings */
void geb_native_fb_NFB3( int mode, STRING_t_P v_si, STRING_t_P v_so, STRING_t_P v_sx, INT_t *v_CONT, INT_t *v_I1, INT_t *v_O1, BOOL_t *v_EN, BOOL_t *v_ENO);


=== func1.c ===


/*
 For functions that returns a string, the output is passed as pointer by caller.
 This function is not thread-safe nor reentrant - but this OK in IEC 61131-3 environment
  */
void geb_native_call_f_NF6STR(STRING_t_P out, STRING_t_P v_s1 ,STRING_t_P v_s2 ) {
	if(VERBOSE) printf("executing geb_native_call_f_NF6STR function call\n");
	/* 	NF6STR := CONCAT(s1,CONCAT('-',s2)); */
	STRING_t_copy(out,v_s1);
	STRING_t_appendbufsz(out,"-");
	STRING_t_append(out,v_s2);
}


/* FB with strings */
void geb_native_fb_NFB3( int mode, STRING_t_P v_Si, STRING_t_P v_So, STRING_t_P v_Sx, INT_t *v_CONT, INT_t *v_I1, INT_t *v_O1, BOOL_t *v_EN, BOOL_t *v_ENO) {
	char buf[17];
	if(VERBOSE) printf("executing geb_native_fb_NFB3 mode=%d\n",mode);
	if(mode==0) { /* init */
		STRING_t_init0_default(v_Si);
		STRING_t_init0_default(v_So);
		*v_CONT = 400;
		*v_I1 = 0;
		*v_O1 = 0;
		*v_EN = true;
		*v_ENO =true;
		STRING_t_setsz(v_Sx,"YES");
	} else if(mode==1) { /* exec */
		/*
		  O1 := I1 + CONT;
		  so := CONCAT(CONCAT(si,INT_TO_STRING(CONT)),sx);
		  CONT := CONT+1;
		  IF sx = 'YES' THEN
		   sx := 'NO';
		  ELSE
		   sx := 'YES';
		  END_IF;
		*/
		*v_O1 = *v_I1 + *v_CONT+1;
		STRING_t_copy(v_So,v_Si);
		sprintf(buf,"%d",(int)(*v_CONT));
		STRING_t_appendbufsz(v_So,buf);
		STRING_t_append(v_So,v_Sx);
		*v_CONT =(*v_CONT+1);
		if(STRING_t_cmpbufsz(v_Sx,"YES") == 0 ) {
			STRING_t_setsz(v_Sx,"NO");
		} else {
			STRING_t_setsz(v_Sx,"YES");
		}
	} else if(mode==2) { /* destroy */
	  /* not used anymore */
	} 
}