About companyProductsShopTechnical support
Moravian Instruments
Main page
About company
Software download
Documents download
Products
The Control Web System
VisionLab Machine Vision Software
DataCam Cameras and DataLight Lighting Units
DataLab industrial input/output system
Scientific cameras
Price list
Services
Custom-made Solutions
Technical support
Control Web Examples

Main pageControl Web ExamplesControl web Documentation

Programming and procedures — OCL

In a general sense, programming deals with ways how to specify algorithms, which will be executed by a computer. Programming has made great progress since the origination of computer science, when it was necessary to put individual instructions into the computer memory, which were than interpreted by the machine. Although present-day computers are not, in principle, able to do more than just execute sequences of simple instructions stored in the memory, the ways of creating these sequences — programs — have dramatically changed.

If you drag the meter virtual instrument from the instrument palette, drop it to the application workspace and modify its properties to measure and display each second some value, you create a program. From the processor instructions point of view, it is a very complex program, including hundreds of thousands of instructions. The reason is simple — there is not an instruction for displaying of a number or for drawing of the meter's pointer. Neither are there instructions for drawing a line (yes, perfectionists can object that specialized graphical processors can draw a line with a single instruction, but it does not make any difference to the principle of our explanation) nor for evaluation of sine function necessary for calculation of co-ordinates of the line end from a known angle. Processor instructions can do very little, for instance, they can read the memory area into the processor's internal register, add two registers or evaluate a condition and jump to another part of the program according to the logical value of the result.

Fortunately the development of programming brought an important invention — high-level programming language. It is a way of writing algorithms on a higher level than machine instructions, by means of symbolic representations. If you want to program the addition of two numbers in a high-level programming language, it is not necessary to know the addresses at which these numbers are stored in the memory, nor is it necessary to assign processor registers to them, simply it is enough to write a + b. How is it possible when the processor understands only its simple instructions? Another program has to be involved in the process — compiler. The compiler converts the high-level language notation, which is usually intelligible to man, into the form of machine instructions intelligible to the computer.

Programs can therefore be made by means of several tools, different in the level of abstraction and independence of a specific platform.

  • The machine code represents direct instructions for the processor of the given computer. Its use is very demanding and inefficient, and the algorithm written by means of machine instructions works only on a specific type of processor and sometimes also on a specific type of computer. These are the main reasons why the machine code (even if it is written using a symbolic assembly-language) is used only exceptionally, mainly when implementing core parts of operating systems. On the other hand, the machine code is the most general means of expression and it can be stated that if something cannot be programmed by means of machine instructions it cannot be programmed at all.

  • The programming language is defined by a set of syntactic rules, which must be followed when writing programs (language grammar) and the source text is then more or less independent of the environment in which the program is running. Compilation of the source text into processor instructions is provided by another program — compiler. There are many programming languages and they differ in the level of abstraction — from hard to use languages close to the machine code (e.g. C language) up to easily used modular languages (e.g. Pascal, Modula-2, Oberon).

  • As a separate group we mention programming languages which, besides having the ability to write any algorithms, are also able to communicate with other components and can work as a “glue” connecting individual components. This property is very important for fast development of applications, because it resolves in the most effective manner the issue of programming — how not to write the same piece of program twice. Traditional languages resolve this problem by introduction of libraries — blocks of the code, which can be added to the program (e.g. calculation of sine function is a typical example of a library code block, which is added to the program if required). However, libraries have many restrictions and only components really solve the problem of reusability. Typical examples of such languages are Visual Basic or OCL (Object Control Language) of the Control Web. Although components can also be typically used from other languages, it usually has an adverse impact on ease and directness of work.

  • Rapid application development tools represent the highest level of abstraction from the principles of processor operation. They usually make it possible to “compose” an application of prepared, parameterized components. Control Web is an example of such a system.

The motivation for implementing OCL into the Control Web is clear: while most applications work as a set of prepared components, sometimes a tool is needed which reaches behind the functionality of present components. OCL can use all instruments (and thus it is not necessary to program the drawing of a measuring instrument indicator) and at the same time it can realize any algorithm and add the required functionality to the application.

Procedure or Method?

Despite the fact that code blocks (algorithm notations) in the Control Web are called procedures, they are a little different from what is called procedural programming described in many classic textbooks. A considerable difference consists especially in the absence of a “main program”, which is started first and possibly calls subroutines. Why is it so? The Control Web does not work in a traditional batch manner. Control Web is an event-driven system. There is no “main program”; instead the system reacts to events and calls code blocks according to given events. But what should we imagine under the term “event”? For common user applications an event is, for example, pressing the key or movement by mouse. However, the Control Web is a real-time industrial system and the set of events is expanded by events generated by the input/output device drivers, virtual instruments or the timer. Thus, the corresponding procedure is called when the given event occurs.

An event never occurs “by itself”, without any relation to an instrument. Every key pressing, mouse movement and also every activation after a time interval or every exception caused by the driver relates to a specific virtual instrument. For this reason, procedures are always connected with a specific virtual instrument too. Such a model is very close to the object-oriented model, within which code blocks realizing object behavior are called object methods. Therefore, procedures could perhaps rather be called instrument methods. However, object-oriented technologies cause complications which should be avoided in tools such as the Control Web, and thus we stick to procedures. One matter is crucial — OCL procedures are always linked with some virtual instrument, they never exist independently..

Procedure interface — parameters and return value

As was said, procedures execute some code — algorithm notation. The algorithm processes data and it would not be of any use unless it read data from outside and returned the results. There can exist two manners of such data exchange which of course can be combined:

  • The procedure may access data elements, which are defined in application data sections (variables, channels, etc.) and read or write their values. Such data elements are called global. Global data elements are accessible from any part of the application, from any virtual instrument and from any procedure.

  • Another manner is definition of parameters of the procedure and possibly their return values. Parameters and return values represent a private interface and it is more advantageous to use them. We will immediately explain why.

There are several problems with access to global data. First of all it would be necessary to define a global variable for each data element to be handled by procedure code, and thus the number of variables grows. Also, orientation in a big number of global variables is considerably more difficult. There is also a risk of the problem of synchronization — one procedure will use a global variable and during a break in execution of the code (caused by the instructions yield, pause or wait — these instructions are described later) another procedure overwrites its value.

For these reasons, each procedure may have the list of parameters — variables and their types, for instance:

procedure Calculate( Value : real; Flag : boolean );

Unlike the definition of global variables, variables representing parameters of the procedure do not have an initializing value — their value is always defined by the calling code. If any procedure wants the procedure Calculate to calculate something, it must provide two values (or expression, which will be evaluated upon call), one is the number of the type real, the other is the logical value of the type boolean.

Therefore, what are the rules for access to variables which appear as procedure parameters?

  • The parameter can have the same name as the already defined global data element (regardless of the type). Then, it is necessary to take into consideration that the use of such an identifier always represents the parameter and the global parameter is hidden, it is not possible to use it. If there is, e.g., a global array of channels of the type real with the name c1 and the procedure has a defined parameter c1 of the type boolean, then in the body of the procedure it is possible to use c1 only as a simple boolean variable.

  • If the parameters of the procedure are defined, these parameters are visible only within the procedure. They cannot be used outside the procedure.

  • No value can be stored in parameters which is to “survive” between procedure calls. The value of the parameters is always assigned when calling them.

When writing more parameters of the same type you need not define each parameter type individually. You can separate parameters by commas and define the type after the colon following the last parameter.

procedure Check( fl1, fl2 : boolean );

Of course, both ways can be independently combined.

procedure TestFlProcedure return valueags( fl1, fl2 : boolean; fl3 : boolean );

If the procedure has no parameters, empty parentheses are placed in the place of the list of parameters. Empty parentheses must be used both in the definition of the procedure also in procedure call notation — parentheses identifies this is a procedure and not data element identifier of something else.

procedure Go(); (* procedure definition *)
...
Go(); (* procedure call *)

Procedure return value

In addition to parameters, the procedure may also contain return value. It is possible to meet a series of built-in functions, e.g. function sin for calculation of the sine of the respective angle etc., when writing expressions in the Control Web. The use of such a function is easy — it is enough to write its name in the expression and to define the parameter (or parameters) in parentheses after the name. When evaluating the expression, the function is called and the value returned by the function (in our case the value of the sine of the respective angle) is used in its place.

Similarly as built-in functions, procedures may return values. If any procedure returns a value, it is possible to call it within any expression. Of course, the return value of the procedure is of a certain type and its use in the expression must corresponds to this type.

It is of course not possible to call the procedure, which does not return any value, in the expression. On the contrary, the procedure returning the value can be called as an independent statement, its return value will be omitted.

If the procedure returns the value, it is necessary to define return type behind the right parenthesis completing the list of parameters and the colon:

procedure CheckValue( Value : real ): boolean;

Remark:

Some programming languages (e.g. Pascal) distinguish between subroutines which do not return value and which do return value by name (procedures and functions). This brings problems when referring (it is necessary to write “procedures and functions”) which results in introduction of redundant restrictions for calling of such procedures and functions — it is not possible to call a function as an independent statement in the program. Therefore, OCL does not distinguish between procedures and functions and the only restriction is impossibility of calling the procedure without a return value within the expression.

The use of procedures with return value is simple. For example, the procedure which tests whether the particular number is in the interval 0..1 will look like this:

procedure Test( a : real ): boolean;
begin
  return (a >= 0) and (a <= 1);
end_procedure;

Remark:

The return statement will be explained later.

It is possible to use the procedure Test any time in the logical expression (it returns the value of the type Boolean):

if Test( a ) then (* fulfilled if the variable a has the value between 0 and 1 *)
  ...
end;
...
Test( 0.5 ); (* syntactically correct call but without any effect – the return value will be omitted *)

Parameters passed by reference

What will happen if a procedure changes parameter value? If parameters are variables, it is possible to write into them in the body of the procedure (to assign them a new value). Therefore, OCL introduces two calling conventions, which distinguish between two kinds of behavior during assignment into parameters inside the procedure:

  • Parameters passed by the value are copied into variables, which represent parameters of the procedures. Because the code of the procedure works with copies of parameters, it is possible to write into them without affecting the data elements passed as parameters. Because the procedure works with local copies, it is possible to transfer any expression of the respective type as a parameter during the call of the procedure. The expression is evaluated before calling of the procedure and the resulting value is stored in the variable of the parameter.

    Warning:

    In the previous Chapter we spoke about restriction of the range of data and about manners of clamping of numbers if they exceed the permitted range of the data type. In the case of parameters of procedures (unlike independently declared data elements) we do not have the possibility to choose between saturating arithmetic and modulo arithmetic and the system will always use saturating arithmetic. If, for example, the value of 1000 is passed into the parameter of the type shortcard, the value 255 appears in the parameter (the maximum number which can be stored in the shortcard type).

  • Parameters passed by the reference work in another manner. No new variable with the value of the parameter is created inside the procedure, but the procedure code works directly with the variable passed as the parameter. This means that any changes in the value of this parameter are performed directly in the data element, which was passed, instead of the local copy during the call. Several restrictions result from it — it is not possible to pass a constant (the procedure would not be able to write the value into it) or an expression to the reference parameter because the expression does not represent one data element.

Parameters passed by the value “disappear” in the body of the procedure and their possible changes will not be reflected outside. In term of the procedure their represent only input parameters. Changes in parameters passed by the reference will appear after completion in the passed data elements, so their represent input and output parameters.

The manner of parameter passing is defined during the declaration of the procedure. Simple notation of the parameter name and its type define parameter passed by value. The keyword var before the parameter name means a parameter passed by the reference. For example, in the declaration of the procedure:

procedure Calculate( var Value : real; Flag : boolean );

the parameter Value is passed by the reference, the parameter Flag by the value. It is not possible to pass a constant or expression in the place of the first parameter, a simple data element is required. Examples of correct and incorrect calling are:

Calculate( 2.5, true ); (* error, constant literal passed as var parameter *)
Calculate( 2 * a, flag1 & flag2 ); (* error, expression passed as var parameter *)
Calculate( a, ~flag1 ); (* correct, if the passed data element a is not a constant and is of numeric type *)

Warning:

If a delay statement pause, yield or wait (these statements are explained below) is used in the procedure, the procedure returns from call with reference parameter values at the time when the procedure executes the delay instruction, not at the end of the procedure.

Remark:

The possibility to pass parameters by reference was not available in the Control Web 2000 and previous versions — procedures always worked with local copies of parameters. Despite this fact, it was possible to transfer changed parameter values back to the data elements passed as parameters. However, this feature was not defined within the definition of the procedure header, but it depended on the code which called the procedure. Let's consider a procedure with one parameter:

the procedure Search( s : string );

If this procedure is called by the command:

Search( Str );

the variable Str remains unchanged irrespective of any changes in the parameter s inside the procedure Search. If the calling code wants to reflect possible changes in the parameter s in the variable Str, it is necessary to use the character & before the parameter name during the calling:

Search( &Str );

Of course, it is not possible to introduce a constant or expression after the & character:

Search( &'hello' ); (* error, reference before the constant *)
Search( &( s1 + s2 )); (* error, reference before the expression *)

This possibility of the transfer of parameters by reference was also maintained in the current version of the Control Web to keep newer versions backward compatible. Nevertheless, it is not recommended to use it. The basic disadvantage of this manner is based on the necessity to use the character & in the notation of each calling, which often resulted in errors. It is also less effective than the use of calling by reference.

This way entirely fails in the case of an attempt to pass parameters to procedures of remote objects. In this case, it is necessary to use var parameters, otherwise changes in the parameters performed in the procedure in remote objects will not be reflected back in the calling code.

In many cases headers of native procedures returning data back into the application were modified so that parameters are transferred by reference in the current version of the Control Web. Users no longer need to write the character & before such parameters, which eliminates number of problems caused by & omission. But this may become a source of minor complications during the port of applications developed in previous versions of Control Web. It was possible to define constant (for example false for logical parameters) instead of the variable, if the application was not interested in particular output parameter. There was not the & character before the constant and the code worked correctly. It is necessary to declare the variable in the current version of the Control Web and pass it in the place of the var parameter, even if the application is not interested in it.

GetWinState( false, &MaxFlag ); (* no error reported in Control Web 2000 *)
GetWinState( MinFlag, MaxFlag ); (* the variable MinFlag is necessary for var parameter
                                    even if the application is not interested in it *)

The previous example demonstrates that it is not necessary to write the & character in the current version of the system and, despite this fact, the variable will be transferred back from the procedure. If, despite this fact, the character & is written before the var parameter, it does not influence the application and the character & is ignored.

Passing arrays to procedures

Because the passed variable is not copied into the local variable during the reference passing of parameters, but only the reference to this valuable is used, it is possible to transfer the whole array as the var parameter. If the whole array is to be passed, the parameter is introduced with keywords array of. For example, the procedure searching for the maximum element of the array may have the header:

procedure FindMax( a : array of real ): real;

It is not possible to pass scalar data element into such a procedure, but it is necessary to pass the whole array. Because arrays in the Control Web may have arbitrary size and may start by any index, two functions returning minimal and maximal array indexes loindex and hiindex were introduced. Both functions require the array identifier (not one element of the array!) as a parameter and return the lowest and the highest index of the respective array. The code in the procedure may handle all elements in the field with the use of these functions. The above-mentioned procedure FindMax could be implemented, for example, as follows:

procedure FindMax( a : array of real ): real;
var
  i : longint;
  max : real;
begin
  max = a[ loindex( a ) ];
  for i = loindex( a ) + 1 to hiindex( a ) do
    if a[ i ] > max then
      max = a[ i ];
    end;
  end;
  return max;
end_procedure;

Warning:

Parameters of type array of are automatically passed by reference, even if there is no var keyword in their definition. It is not possible to pass the whole array by value (to copy the array into the local variable). Any changes in values of array elements are performed directly in the passed field.

Procedure signature

It is necessary to identify procedures (to mutually distinguish them) in many cases. Procedure signature is used for this purpose. For example, it is not possible to declare two identical procedures within one instrument because then it would be impossible to decide which procedure should be called.

The simplest way how to define procedure signature would be declaring its name as its signature. Then it would be impossible to declare two procedures of the same name. However, if you need two procedures, which do the same thing, and only have different parameters there is no need to create a new name. That is why procedure signatures are created not only by the procedure name but also by its parameters within the Control Web. Thus, you can define, for example, two procedures:

procedure SetColor( color : cardinal );
procedure SetColor( red, green, blue : shortcard );

Although both procedures have the same name, they have different parameters and thus it is not a problem to distinguish them during calling. If you call SetColor with one parameter, the first procedure will be called; if you call the procedure with three parameters, the second one will be called. An attempt to call SetColor with different number of arguments or with arguments of different type will cause a compilation error.

Warning:

Automatic conversion of numeric types is performed in Control Web. In practice, it means that, we can for example assign variable of type real to the variable of type integer (of course with the risk of losing information, for details see the sub-chapter about expressions) and it is possible to pass the variable of the type cardinal into the procedure with the parameter of the type real.

Automatic conversion of numerical types is very comfortable, but it has one consequence, which you must bear in mind. It means that signatures of procedures with the same number of numerical parameters (no matter whether they differ) are exchangeable and thus they cannot be defined in one instrument.

procedure SetColor( color : integer );
procedure SetColor( color : real ); (* Error, "redefined procedure", numeric types are replaceable. *)

Procedure names are like all other identifiers in the Control Web case sensitive. This means that

procedure SetColor( color : cardinal );
procedure setColor( color : cardinal ); (* name begins with a small letter, it concerns another procedure *)

are different procedures with different names.

Note that the signature consists only of the procedure name, parameter types and the type of return value. The own names of the parameter do not influence the signature. Therefore, the procedures

procedure SetColor( color : cardinal );
procedure SetColor( c1 : cardinal );

have an identical signature.

Procedure local data

You already know that if a procedure has parameters, these parameters can be referred to as a special kind of variables. The specialty is that if the parameters have the same names as global data elements, they have priority in use and global data elements are made invisible. Another specialty is that parameter variables are visible only from inside the procedure.

These properties are useful for many algorithms. For instance, there is no reason why a variable used as loop counter should be visible also in other procedures. Moreover, if execution of the procedure pauses within the loop, some other instrument can change the variable value and the algorithm will not work correctly. It would definitely be advantageous to reserve some space inside the procedure for such a variable, so that other instruments do not see it and so that it does not occupy a globally visible identifier, which can also be used otherwise. Therefore, it is possible to declare local variables and constants within procedures.

Definition of local variables and constants must appear in the procedure notation before the proper procedure code (the following sub-chapter deals with the manner of notation in detail). Local variables and constants can be used in the same way as procedure parameters, with the exception that they do not create the procedure interface and their values cannot be, unlike parameters, set or read outside the procedure. As in the case of parameters, a global data element of the same name as local data element becomes invisible.

Local variables considerably differ from parameters by their value at the beginning of procedure execution. Whereas parameter values are assigned by whoever is calling the procedure, local variables will have their initialization value stated in the declaration.

However, in many cases it is an advantage if the value of the local variable between individual callings is not forgotten. For example, if you wish to store a value for the whole period of the application's running in the local variable, it would be wrong if this value was set on the initial value upon each calling of the procedure. Therefore, it is possible to declare the local variable by means of two keywords — var and static. Whereas the variable declared after the word var will always be initialized at the beginning of the procedure, the variable declared after the word static will be static, the initializing value will be set during the starting of the application and then the last assigned value will be preserved.

Warning:

If you want the value of the local variable to be preserved also between individual callings of procedures, you must declare it after the keyword static, not var.

Notation of procedure code

Procedure notation must correspond to the following grammar. Terminal symbols are written in small letters, non-terminal symbols in capital letters:

PROCEDURE   -> procedure ( PARAMETERS ); { DECLARATION } begin BLOCK end_procedure ;

PARAMETERS  -> [ name { , name } : TYPE ] { ; name { , name } : TYPE }

DECLARATION ->   label  LABEL_LIST ; 
               | const  CONST_LIST ;
               | var    VAR_LIST ;
               | static VAR_LIST ;

LABEL_LIST  -> [ Identifier ] { , Identifier }

CONST_LIST  -> [ Identifier = Value ] { ; Identifier = Value }

VAR_LIST    -> [ Identifier : TYPE ] { ; Identifier : TYPE }

TYPE        ->   boolean
               | shortreal
               | real
               | shortint
               | integer
               | longint
               | shortcard
               | cardinal
               | longcard
               | string

BLOCK       -> [ STATEMENT ] { ; STATEMENT }

STATEMENT   ->   if EXPRESSION then BLOCK
                 { elsif EXPRESSION then BLOCK }
                 [ else BLOCK ]
                 end
               | switch EXPRESSION of
                 { case CONST_EXPRESSION [ , CONST_EXPRESSION ] ; BLOCK }
                 [ else BLOCK ] end
               | loop BLOCK end
               | while EXPRESSION do BLOCK end
               | repeat BLOCK until EXPRESSION
               | for SimpleNumericVariable = EXPRESSION to EXPRESSION [ by CONST_EXPRESSION ] do BLOCK end
               | goto Identifier
               | pause EXPRESSION
               | yield (* alias for pause 0 *)
               | wait EXPRESSION
               | commit
               | send Identifier { , Identifier }
               | sound EXPRESSION
               | return [ EXPRESSION ]
               | Identifier : [ STATEMENT ]
               | DataElement = EXPRESSION
               | move ArrayDataElement , ArrayDataElement, EXPRESSION
               | MethodName ( PARAM_LIST )
               | self . MethodName ( PARAM_LIST )
               | ObjectName . MethodName ( PARAM_LIST )
               | DataElement -> MethodName ( PARAM_LIST )
               | (* empty statement *)

(* this rule is valid only in block between 'loop' and 'end' *)
STATEMENT   ->   exit
               | continue

PARAM_LIST   = [ PARAMETER ] { , PARAMETER }

PARAMETER    =   EXPRESSION
               | DataElement
               | Array

Each procedure starts by the declaration of the header

procedure ProcedureName( list_of_parameters );
procedure ProcedureName( list_of_parameters ): return_value_type;

The heading is followed by declarations of blocks separated by the character ; (semicolon). In total there are five types of blocks:

  • Block label declares the labels used in the procedure.

  • Block const declares local constants.

  • Block var declares local initializing variables.

  • Block static declares local static variables.

  • Block begin introduces code of the procedure.

Blocks of declaration of labels label, constant const, initialized variables var and static variables static may be defined in any sequence order and may arbitrarily repeat or they need not be mentioned at all. But it is necessary to bear in mind that each symbol must first be declared and then used. For example, the notation

var
  a : real {init_value = init_a}; (* error, symbol init_a is not known *)
const
  init_a = 1;

is incorrect. The constant used for initialization of the variable must be defined earlier.

const
  init_a = 1;
var
  a : real {init_value = init_a};

The block defining the own code of the procedure begin must be always present, must be the only one and must be the last one. This block always terminates with the keyword end_procedure; which also closes the whole procedure notation.

Labels

If the goto instructions are used within the program, it is necessary to declare all identifiers used as labels after the keyword label first of all. Individual identifiers are separated by commas and the whole label declaration ends with a semicolon. The destination of the jump is marked by using the label followed by a colon before the statement, which is to be executed after the goto statement.

Example:

procedure Test();
label
  Error;
begin
  if a < b then
    goto Error;
  end;
  ...
Error:
  sound "ALARM.WAV";
  ...
end_procedure;

Declaration of local constants and variables

As mentioned above, constants are declared after the keyword const, initialized variables after the keyword var and static variables after the keyword static. Notation of local constants and variables is syntactically identical with the notation of global constants and variables.

Constants are declared in the following form:

const
  constant_name = value;

Constant type is not defined, it is derived automatically from its value.

Variables are defined in the form:

var
  variable_name : type { attributes };

Procedure body

The body of the procedure introduced by the keyword begin contains individual statements separated by the character ; (semicolon). The body terminates with keyword end_procedure and the end of the procedure body is at the same time the end of the declaration of the whole procedure. Statements can be divided into two types:

  • Control statements, which determine the execution of other statements, e.g. conditions, loops or other procedure calls.

  • Executive statements, which influence states of variables, activate other instruments etc.

Statement if

The if statement serves for conditional performing of certain parts of the program. After the keyword if there must be a logical expression and the keyword then. The following block of statements is executed only if evaluation of the logical expression returns the value true. The block of statements ends in three ways:

  • By the keyword end. In the case of non-fulfillment of the condition, the program continues with the statement after this keyword.

  • By the keyword else. After else there is a block of statements, which will be executed only in the case of non-fulfillment of the condition. This block of statements must end with the keyword end.

  • By the keyword elsif. After this keyword there is the condition and the keyword then and the block of commands, the same as after if.

This combination allows selection of a single variant in dependence on various conditions. The block of statements following the first fulfilled if or elsif condition is executed and possible following elsif conditions are not even tested. If no condition is fulfilled, the block of statements after the keyword else is executed (if present). If there is no else section, the program continues execution after the keyword end.

Example:

if i < 10 then
  i = i + 1;
end;

if i < 0 then
  di = 1;
  i = 0;
else
  di = -1;
  i = 100;
end;

if ErrorCode = 0 then
  ErrorMsg = 'Error in reading of the channel';
elsif ErrorCode = 1 then
  ErrorMsg = 'Error in writing of the channel';
elsif ErrorCode = 2 then
  ErrorMsg = 'Communication error';
else
  ErrorMsg = 'Unknown error';
end;

Remark:

The conditional expression if is a generalization of conditional expression containing built-in function iif. If there are single assignments to the same data element after then and else, it is possible to replace conditional statement:

if a < 100 then
  a = a + 1;
else
  a = 0;
end;

with conditional expression:

a = iif( a < 100, a + 1, 0 );

Statement switch

The switch statement divides the execution of the code on the basis of the value an expression. If the value of one expression is tested, like for instance the value of the variable ErrorCode in the previous example, the use of the switch statement is easier and its execution is faster then a series of commands if elsif.

switch ErrorCode of
case 0; 
  ErrorMsg = 'Error in reading of the channel;
case 1;
  ErrorMsg = 'Error in writing of the channel';
case 2;
  ErrorMsg = 'Communication error';
else
  ErrorMsg = 'Unknown error';
end;

Similarly to the if statement, the else block is not mandatory. If the tested expression does not result in any of the values listed after the keywords case and the branch else is missing, no branch of the command switch will be performed.

It is not necessary to write only one value after the keyword case, it is possible to list more values separated by commas, e.g.

switch InputValue of
case 0, 1, 2, 3;
  ...
case 10, 100, 1000;
  ...
end;

Warning:

Values after the keywords case must be constant (the are calculated at compile time). If it is necessary to compare the expression with general expressions, it is necessary to use the if elsif statement.

Statement loop

The loop statement implements general loops. If you do not want this loop to be endless, you must finish it by the exit statement. The keyword exit is valid only inside the cycle loop (between loop and end keywords). There is a block of statements after the keyword loop ending with the keyword end, which will be cyclically executed.

The loop statement can be used if the loop exiting condition should be evaluated inside the block of statements or if there are more places for leaving the loop. If the ending condition should be evaluated at the beginning or the end of the cycle, it is better to use while or repeat until loops. If the cycle is to be repeated for a certain number of loops known in advance, it is more convenient to use the for loop.

If there is a loop located inside another loop, the exit statement will cause the inner loop is being left. It is also possible to use the continue statement inside the loop. This statement causes transferring of control at the beginning of the loop, that is jumping over the rest of the statements in the loop. In the case of more loops, the command continue relates to the inner loop.

Example:

loop
  a = b + c;
  if a > 123 then
    exit;
  end;
  i = i + 1;
  if i > 100 then
    exit;
  end;
  if i < 20 then
    continue;
  end;
  j = i;
end; (* loop *)

Remark:

Keep in mid that if there are no pause, yield or wait statements within loop loop, next pass through the loop (including evaluation of the exit conditions) will not cause communication with I/O devices and channel values will not change.

Statement while

This statement is a special case of loop. There is the logical expression after the keyword while followed by the keyword do. If the logical expression is evaluated to true, the following block of statements is executed up to the keyword end.

If the condition is not fulfilled even for the first time, the statements inside the loop will not be executed at all.

Example:

i = 0;
while i < 10 do
  a = a + c;
  i = i + 1;
end; (* while *)

Statement repeat until

The keyword repeat introduces the beginning of the block of statements. This block ends with the keyword until followed by the logical expression. After execution of the block of statements the program tests the condition after until and if the result is false it will start to execute the block of statements again.

If at the beginning the logical expression has the value true the block of commands will be executed at least once.

Example:

i = 0;
repeat
  a = a + b;
  i = i + 1;
until i >= 10;

Statement for

The for statement introduces the counted cycle. There is numeric variable after the keyword for, the “assign” symbol = and the numeric expression. Then, there is the keyword to and the next numeric expression followed by the keyword do and the block of statements of the body of the loop ending with the keyword end. When entering the cycle, the numeric variable will be set to the value determined by the expression following the equal sign. Then the value of this variable will be compared with the value of the expression following the keyword to. If the value is smaller or equal to this value, the body of the cycle will be executed up to the corresponding end keyword, the control variable will be increased by one and the comparison will be repeated. If the initializing value of the control variable is greater than the expression after to, the body of the cycle will not be executed.

Example:

for i = 1 to 10 do
  a[ i ] := i * i;
end;

for i = 3 to 2 do
  (* body of the cycle will not be executed *)
end;

It is not always required that the control variable should increase by 1. Sometimes it is necessary to increase the step or decrease the control variable. For this purpose, it is possible to define the step of the control variable after the keyword by before the keyword do.

Example:

for Alpha = 0 to 360 by 10 do
  x = sin( Alpha / 180 * Pi );
end;

for x = 10 to 1 by -1 do
  y = x * 10;
end;

Warning:

The expression defining the step after keyword by must always be constant. This means that it must not contain any variables or channels.

Statement yield

As has already been said, the whole procedure body is executed in a single time step, that is, with the same value of input/output channels. However, sometimes it is necessary to end the time step and enable the system to measure new values of input/output channels. The command yield will finish executing the procedure in the current time step and provide time for the system to activate all other instruments which are to be activated in the running time step. Executing the procedure will continue in the nearest system time step. This does not mean the next activation of the instrument, which implemented this procedure, but the nearest system tick.

Example:

while Level < 12.5 do (* waits for the achievement of the level *)
  yield; (* termination of the time step and making possible communication for changing the new value *)
end; (* while *)

Control1 = true; (* closing the valve *)
pause 5; (* waiting for emptying the pump *)
Control2 = false; (* switching off of the pump *)

Statement pause

This statement serves for stopping of the activity of the program for a certain period. There is a numeric expression stating the number of seconds of interruption of the program after the pause keyword. If the execution approaches the pause statement, it will stop the execution for the defined period and then continue by the next statement. The statement pause 0 does not stop the program, but it provides the system with time for possible servicing of other instruments (the same function is performed by the statement yield).

Example:

while Level < 12.5 do (* waiting for reaching the water level *)
  pause 0; (* can be substituted with yield *)
end;

Control1 = true; (* closing the valve *)
pause 5; (* waiting for emptying of the pump *)
Control2 = false; (* switching off of the pump *)

Statement wait

This statement will interrupt the running of the program until the fulfillment of the condition (boolean expression) following the keyword wait. If the value of the expression is true the program continues with the next statement in the same time step. If the value of the expression is false the program provides the system with time for possible servicing of other instruments and performing th I/O operations and tests the condition again. An example of description of the yield statement would be possible to write more efficiently:

Example:

wait Level >= 12.5; (* waiting for the achievement of the level *)

Control1 = true; (* closing the valve *)
pause 5; (* waiting for emptying of the pump *)
Control2 = false; (* switching off of the pump *)

Statement commit

The commit statement serves for initiation of communication (writing and reading of channels) without the loss of the time step. The use of the commit statement has a similar effect to, e.g. the conditional command containing the channel in the condition — channels indicated for reading are read and channels with a written value are written into the driver.

A similar effect to commit have for instance yield or pause statements, however, these statements will end the execution of the time step and will cause continuation of the algorithm in the next step. The commit statement only initiates communications and the algorithm continues within the same time step.

Details of the function of the command commit are described in the sub-chapter Use of channels in procedures.

Statement return

The return statement has two forms depending on whether the respective procedure returns some value or not. If the procedure does not return value, the command return will cause termination of the execution of the procedure and return to the code, which called the respective procedure. Another call to the procedure will start it again from the beginning.

procedure Divide( a, b : real; var c : real );
begin
  if b = 0 then
    c = 0;
    return;
  end;
  c = a / b;
end_procedure;

However, if the procedure returns some value (it may be called within the expression), then there is an expression whose value is passed as a return value of the procedure after the return keyword.

procedure Divide( a, b : real; var c : real ): boolean;
begin
  if b = 0 then
    return false;
  end;
  c = a / b;
  return true;
end_procedure;

Statement send

There is a list of identifiers separated by commas indicating the names of instruments in the application (of course, the list can be short and contain only one instrument) after the keyword send. This command will ensure activation of the specified instrument(s) immediately after finishing the actual time step. If you want to activate the same instrument as the one implementing the given procedure, you can write its name or use the substitute identification self. The statement send self means “activate itself in the nearest next system time step”. This activation will perform all actions (communication with input/output devices and actualization of channel values is executed before it) and it practically equals to inserting another time step for all instruments listed in the send statement.

There is an equivalent of the send statement in some other instruments in the form of the property receivers followed by the list of instruments. If more instruments have the same name and this name is mentioned in the send statement, all instruments with this name are activated.

Remark:

This statement is very powerful and can save a big amount of calculation performance. If, for example, some variables control only the visibility of panels, it is not necessary to time these panels to periodically check variable state, but they can be notified by the send statement only during their change.

Example:

Panel1Visible = (KeyInput = F1);
Panel2Visible = (KeyInput = F2);
send Panel1, Panel2;

A similar procedure can also be used for instruments using the command receivers, e.g.: multi_switch or panel with active rectangles etc.

Statement sound

This statement ensures the playing of sound files of WAV format. There is a string expression which represents the name of the file containing the sampled sound after the keyword sound. Of course, the functionality of this statement is conditioned on the presence of a supported sound card in the computer and the correct installation of their drivers in the Windows environment. If sound support is not correctly installed, the statement will be ignored.

Example:

switch Alarm of
case 1;
  sound 'fire.wav';
case 2;
  sound 'explosion.wav';
else
  sound 'quiet.wav';
end;

Statement move

The move statement serves for mutual assignment of arrays or their parts. The use of move is always faster than assigning individual array elements e.g. using the program loop. Considerable speeding up is reached especially when transferring the channel array into the variable array. When assigning array elements into variables using the program loop, the program is always made to ask the system core to measure each element of the channel separately, because generally a new value of the measured element of the channel array can influence the index of the subsequent element of the array.

Statement syntax begins with the keyword move, followed by the first element of the array, which is to be read (source). The first element of the destination array (target) is separated by comma. Another coma separates an expression for the number of elements, which are to be moved.

Example:

move source_array[ i ], destination_array[ j ], count;

This command moves in total count elements of the array source_array starting from the element with the index i to the array destination_array from the element with the index j.

Assign statement

The assignment statement can occur anywhere in algorithm notation. It has the form data_element = expression. The target data element must be writable (it cannot be e.g. constant). There is an arbitrary expression on the right side of the equal sign, whose type must correspond to the data element type on the left from the equal sign. A boolean expression must correspond to boolean data element. A string expression must correspond to string data element. If the data element is numeric (the type shortint, integer, longint, shortcard, cardinal, longcard, shortreal, real), it is possible to assign result of any numeric expression to it.

Warning:

If the type of data element is not able to take the result of the expression, the information can be lost. The decimal part of the number is cut away when converting to integers; big numbers can be clamped at the highest significant places.

Example:

(* numeric expressions *)
i = i + 1;
x[ 12 ] = ( k[ j ] + k[ j + 1 ] ) / ( atan2( theta, ksi ) * 2 );
a[ i ] = fi * cos( beta ) / PiBy2;
b[ i + 1 ] = fi * sin( alfa ) / PiBy2

(* logical expressions *)
result = value <= ( a + 1 );
ExitCondition = true;

(* string expressions *)
Message = 'Error: ' + ErrorString;
Title = 'Demonstration panel';

Remark:

Users often use the assign statement only with numeric data elements and expressions, they hesitate to use it for example with logical values (with exception of simple assignment of true or false). It is often possible to meet constructions of the type:

if a = b then
  result = true;
else
  result = false;
end;

This notation works correctly, but the same effect can be achieved easily and quickly by one assignment:

result = a = b;

The occurrence of two characters = one by one can be misleading, but their meaning is quite exactly defined. The first = has the meaning of the assignment, the second = is logical comparison.

Calling of procedures

Previous examples were concentrated on how to write procedures, how to control their execution, how to work with local data and how to pass parameters into and from procedures. The final thing you need to know is how to call procedures. It is actually a closed issue, because procedures are called from other procedures, and therefore notation of this calling simultaneously belongs to description of the procedure's internal parts.

If procedure calling was written in the previous text, it was in the form

Go();

This calls the procedure Go() of the same instrument or section which calls it. If the respective instrument or section does not have the Go() procedure, an error in compiling will occur. If you want to call a procedure of another instrument or section, you have to write the name of the instrument (section) implementing this procedure before procedure name. It has already been said that procedures cannot be implemented outside any instrument or section.

InstrumentName.Go();

The procedure can be also called using instrument pointers.

InstrumentPointer->Go();

If the pointer doesn't point to an existing instrument, or points to an instrument that doesn't have the Go() procedure, a critical error will occur and the execution of the application will be halted.

If you are calling a procedure of an instrument which is in another module, the module name must also be before the instrument name

ModuleName.InstrumentName.Go();

Remark:

It was always necessary to write the instrument name before the procedure name in Control Web 2000 and previous versions. Even in cases when the code of one procedure calls another procedure of the same instrument, it was necessary to write the instrument name before the called procedure name. However, this is not very comfortable and it can also cause problems if you rename the instrument or if you copy the procedure into another instrument — then it is difficult to decide if the given calling should be redirected to another instrument or left. Therefore, it was necessary to introduce the keyword self. This keyword in each procedure always substitutes the instrument in which the procedure is implemented.

Example:

meter m1;
  ...
  procedure Go();
  var
    a = real, 0;
  begin
    ...
    m1.SetValue( a ); (* calls the procedure SetValue for itself *)
    self.SetValue( a ); (* works in the same manner as previous calling,
                           but also functions after renaming of the instrument *)
    ...
  end_procedure;
  ...
end_meter;

The keyword self is no longer necessary and simple indication of the name of the procedure works identically.

Several facts must be taken into account when calling procedures:

  • Unlike the activation of instruments by the statement send, which practically represents insertion of another time step following immediately after the actual time step and including communication with input/output devices, procedure call is always performed immediately in the current time step and, hence, just the actual values of channels are evaluated. Calling a procedure to an instrument will not cause reading of a new value of the evaluated channel of the input/output device, unless reading is called by some other instrument evaluated within the same time step.

  • Procedure call is a blocking operation. It means that executing of the code is transferred from the calling procedure to the called procedure and the calling procedure will continue with the instruction following the call statement as late as after finishing the called procedure and returning from calling.

  • If the execution of the procedure is stopped by the instruction pause, yield or wait then the procedure returns from calling into the called procedure and the calling procedure continues in its running without the completion of the code of the called procedure to the end. A such called procedure continues in the nearest system time step (after yield), after expiration of the defined period (after pause) or after the fulfillment of the condition (after wait) independently in the execution of its code, as if it were activated, e.g. by the command send, i.e. including measurement of channels, etc.

  • Procedures cannot be called recursively. A procedure thus cannot call itself once again, not even through other procedures! It is necessary to realize that the call chain can be very complicated and recursion can occur after many other embedded calls.

Comments

Comments can be used anywhere in procedure notation. The comment is introduced by a comment parenthesis (* and finished by the opposite parenthesis *). Whatever is stated in these brackets is jumped over during compilation. Thus, comments can be used not only for explaining the meaning of procedures or individual statements, but also for eliminating parts of code.

Comments can be nested in Control Web. The depth of nesting is evaluated and the comment ends with the last commentary parenthesis.

Example:

i = 0;
(* comment with the depth 1
(* depth of comments is 2 *)
the first commentary bracket is still valid *)
i = i + 1;

Of course, comment parentheses can be used wherever in the text notation of the Control Web application. With regard to the dual development environment, however, comments cannot be preserved in graphical mode, for the application source text totally lapses. Procedures are an exception in this respect and store comments also when changing over between text mode and graphical mode.

Always (un)true expressions in conditions

Syntax of many statements requires the presence of a logical expression, which influences the behavior of the statement (e.g. the while statement requires a logical expression, which states whether the loop is performed or not). Restriction of the range of data types, about which we spoke in previous Chapter, may principally influence the fulfillment (or non fulfillment) of conditional logical expressions. Consider the following example:

var
  c : cardinal;
begin
  c = 10;
  while c >= 0 do
    ...
    c = c - 1;
  end;
  ...

The while loop in this example will be infinite, because the data element of the type cardinal cannot contain a number smaller than 0. Similarly, the condition:

var
  a : shortint;
begin
  if a > 200 then
    ...

will never be fulfilled because the maximum number in the data element of type shortint is 127 and can never be higher than 200. There are many examples of expressions, which are permanently true or false also in other examples repeat until, for to do, wait, ...).

The compiler in some cases (e.g. if the data element is compared with the constant) detects that the expression is always true or false and writes a warning (not error in compiling) during compilation. But it is not possible to decide the unchangeability of the condition during compiling in many cases, because it results from the logic of the application. Therefore, it is necessary to remember this possibility during debugging of the application or, better, during its design.

Hint:

The compiler performs many optimizations at application startup only; optimizations cannot be performed during togging of IDE mode or test compilations. One form of optimization is calculation of constant sub-expressions. If there are (non-)fulfilling conditions consisting of the data element compared with the constant expression in the application, a warning about permanent (non-)fulfillment of the condition appears only at application startup, not during IDE mode togging.

Event and user Procedures

As you already know, each procedure can be called from another procedure. However, sometimes it is necessary to call at least some procedures by another mechanism, otherwise the calling system would never “relive”, no one would start with the first calling.

Of course, such a mechanism exists and was mentioned in the introductory part of this chapter. The Control Web does not have any “main program”, instead it activates pieces of the code on the basis of actions of user or system events. Procedures are also classified into this context and many procedures are called when certain conditions are fulfilled — of course, if you program these procedures.

All procedures implemented by you within each instrument can be subdivided into two groups:

  • Event procedures. If these procedures are implemented, they are called directly by the system without the assistance of other procedures when the corresponding event occurs.

  • User procedures. The Control Web itself does not call these procedures. If these procedures are to be activated, they must be called from some other procedures, either user or event.

The specific instrument of the Control Web can activate (call) an event procedure only if it recognizes it. Event procedure must have a signature corresponding to event signature defined by particular instrument to be called on event occurrence (procedure signatures were described above). It thus has to have a name (case-sensitive!) and parameter types corresponding to the event procedure definition. Then it is recognized by the instrument and called upon the occurrence of the corresponding event.

Example:

switch sw1;
  ...
  procedure OnOutput( state : boolean );
  begin
    (* this procedure will be called on each input action of the instrument switch *)
    ...
  end_procedure;
  ...
end_switch;

Signatures of event procedures and conditions of their calling are described with the respective instrument documentation. If you open the inspector above the instrument and click on the Procedures tab, the list of all event procedures of the respective instrument will appear. Moreover, you have possibility to complete any user procedures, but as was already said, you yourself must take care of their calling.

There is a group of event procedures which are common to all instruments. It does not mean that all instruments will call all procedures from this group. However, if the given procedure has sense for the given instrument, the instrument will call it. These basic event procedures will be described in more detail when describing the key event procedure OnActivate.

OnActivate() event procedure

OnActivate is an exceptional event procedure. The first reason is that it is the only procedure always called when the instrument is activated. Practically all instruments carry out their activities within their activation. If you for example define an expression for the instrument meter, which will be calculated and the result will be assigned to some data element, this calculation, assignment and possible redrawing of the meter instrument with the new value is carried out within the activation. Within its activation the alarm instrument tests the exceeding of limit values and the archiver instrument records data, the instrument draw animates its drawing, etc.

Only instruments, which react to e.g. user actions or communication events, perform some activity outside activation intervals. For example the control instrument writes the value into the data element after user action without being activated. Similarly the httpd instrument, working as a WWW server, sends data upon the client's request, without waiting for activation.

Instruments can be activated in several ways. Activation differs considerably in real-time applications and in data driven applications.

In real-time applications the instrument can be activated by these impulses:

  • Periodical timing by a value or timer.

  • Exception caused by another instrument (activation by instrument).

  • Exception caused by a driver (activation by driver).

In data driven applications periodic timing is permitted only for some instruments, whose activity depends on exact keeping of the time interval, e.g. for the instrument archiver or pid_regulator. Activation by exceptions from both instruments and drivers is maintained. However, a new impulse is added:

  • Exception caused by data change.

If you declare a procedure with the following signature:

procedure OnActivate();

this procedure will be called during each activation, more exactly before each activation of the instrument. The procedure OnActivate has the possibility to test and modify data elements influencing instrument operation.

The second reason for the exceptionally of procedure OnActivate is the fact that this procedure can correspond to several signatures, even if only one procedure is in question. The reason for this exception is simple. In some situations it can be useful to know the reason for procedure activation. Then you can write the procedure OnActivate in the form:

procedure OnActivate( ByTimer, ByInstrument, ByDriver, ByData : boolean );

In this form the procedure OnActivate receives four logical parameters stating the reason for activation. The parameter ByData can be true only in data driven applications. The code of the procedure OnActivate can then test the cause of activation and perform an operation only e.g. during periodical activation or when called by exception from the driver etc.

Coincidence of several reasons for activation can occur in certain situations. For example, an instrument activates another instrument and within the same time step there will also be time for the instrument's periodical timing. Then the instrument is activated only once, the logical value true is set in two parameters — ByTimer and ByInstrument.

However, Control Web can provide much more information to the instrument than just the reason for activation during activation. Still, it is not possible to pass all data as independent parameters. That is why this information is passed in the form of the so-called bitmask. The bitmask represents one number whose individual bits (binary digits, having either the value 0 or 1) bear logical values, the same as logical parameters. To find out whether the corresponding bit of the respective number is set, it is possible to use the function bitget. If, for example, you need to execute some action in the procedure OnActivate only during the activation of the instrument from the driver may you write the procedure OnActivate in two manners:

procedure OnActivate( ByTimer, ByInstrument, ByDriver, ByData : boolean );
begin
  if ByDriver then
    (* this code is executed only during the activation from the driver *)
  end;
end_procedure;

procedure OnActivate( ActivateMode : longcard );
begin
  if bitget( ActivateMode, 2 ) = 1 then
    (* this code is also executed only during the activation from the driver *)
  end;
end_procedure;

Although both ways of notation will work in the same manner, the second way is a little more complicated than the first one. So why the third form of OnActivate exists? Because you can find more information by testing of other bits in the parameter ActivateMode than the reason for activation. The meaning of individual bits is as follows:

ObjectDescription
bit 0

the instrument is activated by the timer

bit 1

the instrument is activated by another instrument

bit 2

the instrument is activated by the driver

bit 3

reserved, always 0

bit 4

the instrument is activated by change in data (only in data driven applications)

bit 5

the instrument is in time delay

bit 6

the instrument is called startup and is just activated as the first instrument of the application

bit 7

the instrument is called terminate and is just activated as the last instrument of the application

bit 8

the application is in time delay (that is not necessarily this instrument but any instrument in the application)

bit 9

the application terminates, the terminate instrument is running

bit 10

the instrument is activated from another module

bit 11–31

reserved for future use

If you use the only parameter ActivateMode in the OnActivate procedure, you may find out, for example, when the application is in time slip or when it has just started.

Summary:

  • The procedure OnActivate is the only procedure called within instrument activation.

  • The calling of OnActivate always happens before the application of the instrument, so it is possible to change data elements processed within own activation.

  • The procedure OnActivate can have three signatures (parameter types), but it is always only one procedure.

  • It is not possible to define more OnActivate procedures within one instrument, even with other parameters (different signatures).

Timing of procedures

Unless stated otherwise, the whole procedure algorithm (from the beginning to the end) is executed in one time step, that is, with identical measured values on the channels. This should be realized especially when programming loops. It is possible to use some of the delay instructions pause, yield or wait. These instructions will cause interruption of the procedure for a certain time, or until a condition is fulfilled. The condition is tested in the following system time step (and not in the time step of the instrument implementing the procedure!) and if it is fulfilled, the procedure continues from the statement following the condition, and not again from the start. In this case, the values of read channels are measured again.

More details about relations between calling and timing of procedures and delay instructions can be found in the sub-chapter Recursive calls and delay statements.

Warning:

It is possible to implement an infinite (or at least a very long) loop very simply. It is necessary to consider that such a loop blocks passing to the next step and the whole application waits for its completion.

Use of channels in procedures

If an instrument evaluates some expression within its time step (e.g. meter) and this expression contains channels, then the Control Web core will ensure measurement of these channels before the evaluation of the expression (of course, if it is possible during the time defined by the channel parameter timeout). A similar situation will occur if channels are used in the procedure OnActivate. Let's have defined variables a1, a2 and a3 and the channels c1, c2 and c3. Then the following part of the code:

a1 = c1;
a2 = c2;
a3 = c3;

will be executed after the system reads values of channels c1, c2 and c3.

Remark:

What will happen if one channel is used more times within one time step? The core will read it only once in each time step. In the case of repeated use within the same time step, the channel is considered measured and is not read again. Repeated measurement of the same channel will “enforce” the delay statement pause or yield. These instructions will cause continuation of the procedure within another system time step. The rule that no channel is measured more than once in one time step is kept.

What will happen if the code looks as follows:

if c1 > 0 then
  a2 = c2;
else
  a3 = c3;
end;

There are all three channel used in the code notation, but the construction of the algorithm rules out all three channels being necessary at once. It will be necessary to use the channel c2 or c3 according to the value of the channel c1, but not all three at the same time. In this case it appears to be an unimportant discussion because the measurement of one channel may not considerably cause delay of the application. However, if there are not individual channels, but thousands or tens of thousands of channels in individual branches, unnecessary communications may represent a serious problem.

For these reasons, the execution of procedures is optimized in the following manner: If branching in the program is found and it is necessary to read some channel(s) for making a decision, the algorithm specifying which channels are necessary to be read will stop and the currently marked channels are read. This will enable evaluation of the conditional expression specifying which part of the procedure will be executed. Searching for the necessary channels will continue only in the selected branch of the procedure.

It is sometimes useful to enforce communication with peripheral devices (reading of marked channels and sending of written channels) within an algorithm. As already described, the simple occurrence of some conditional command (e.g. if or while) will ensure it. At the same time, the delay instruction (yield, pause or wait) will cause communication, but with the interruption of the algorithm and continuation in the next system time step. However, if it is necessary, e.g. to write more values in a sequence order and, at the same time, not to lose the time step, it is possible to use the statement commit. The occurrence of this statement in the program does not influence execution of the algorithm, it will only cause forced communication with the device within one time step.

cmd = 'init';
commit; (* the string 'init' will be recorded in the channel  *)
cmd = 'start';
commit; (* the string 'start' will be recorded in the channel *)
...

Two consecutive writes would cause overwriting of the string if we did not use the commit statement.

Warning:

There is a principal difference between yield and commit statements, demonstrated in the following examples. Let's consider part of the code

a1 = c1;
commit;
a2 = c1;
commit;

The commit statement will cause reading of the channel c1. The assignment after commit again requires reading c1, but because the algorithm continues in the same time step, the system will not read the channel c1 irrespective of the next commit, because this channel has already been read.

a1 = c1;
yield;
a2 = c1;
yield;

In this case, the channel c1 will be really read 2× because the algorithm will be divided into more time steps.

This restriction is not valid for writing, which is performed the same number of times as the number of occurrences of the algorithm.

Recursive calls and delay statements

It has already been emphasized that procedures cannot be called recursively, meaning that if a procedure is unfinished and has not yet run to the end, it cannot be explicitly recalled. Under normal circumstances the system guarantees that no other procedure can be started until the currently running procedure is accomplished. Still, there exist several possibilities how recursive calling can occur, for instance, a procedure called from another procedure calls again the calling procedure. However, the calling procedure is waiting for the finishing of calling of another procedure and is thus unfinished. Such calling will be rejected and a warning will be written into Log Window. It is important to realize that the recursion does not have to be so simple, the called procedure will call another procedure, which can call again another procedure and the calling procedure can be called again as late as on another level. This situation is also not permitted.

The situation with recursive calling will be made much more complicated if the delay statements pause, yield or wait are used in procedures. Such instructions will interrupt execution of the procedure and will bring execution back into the calling procedure. The called procedure will remain in unfinished status and cannot be called again until it runs to the end.

The only exception is the implicitly called procedure OnActivate which in this case continues in testing of the condition for running and possibly continues from the place of interruption. If the procedure OnActivate waits for some delay statement and some driver activates the respective instrument, the procedure OnActivate “consumes” this activation for testing of the condition of the further run, not for starting from the beginning and possible testing of the reason for activation! If some delay statement is used in the procedure OnActivate and, at the same time, the procedure decides according to the condition of the activation, then according to time relations there can be omission of treating of the activation.

Frequent problems can originate especially when using delay statements in event procedures. If execution of an event procedure is interrupted and the given event occurs again, recursive calling will happen again.

To avoid the above-mentioned problems with recursive calling, OCL has a possibility to restrict the use of delay instructions.

  • The delay instructions pause, yield and wait can be used exclusively in the procedure OnActivate. These instructions are not permitted in any other event or user procedures.

  • The procedure OnActivate cannot be called explicitly. It can only be called implicitly within instrument activation.

Because this restriction can be very unpleasant in many cases, you may take responsibility for not calling procedures recursively and cancel this restriction by setting the parameter independent_procedure_execution in the settings section to true. It is possible to set this parameter in the tab Data Inspectors — System in the integrated development environment. You can use delay instructions and call the procedure OnActivate without restriction in this case.

Instrument local data

We have already mentioned the advantage of using local variables and constants within the procedure. One of the key advantages is that local data are visible only from inside the procedure, they do not “mess” among global data elements visible from all instruments.

However, if you define more procedures, which somehow cooperate with the instrument, the mechanism of variables’ locality will make them invisible even among procedures of the same instrument. The problem is that procedures within one instrument need to share variables invisible to other instruments. This can be to a certain extent compensated for by storing necessary information in local variables of the selected procedure and passing it to other procedures as parameters. This process, besides being cumbersome, will fail if event procedures, whose parameter list (signature) is determined in advance, also need to access such variables.

Therefore it is possible to declare instrument local variables and constants in blocks const, var and static as within the body of the procedure. Thus, all constants and variables declared within the instrument can be seen by all procedures of the given instrument, but they are invisible from outside.

Unlike the body of the procedure, where the block defining constants and variables is automatically terminated by the beginning of the other block, the instrument blocks const, var and static must end in the respective keywords end_const, end_var and end_static.

Local data elements declared within the instrument can be used also in notation of instrument expressions. However, if these are used in an expression or procedure, the block where these elements are declared must precede their use. Variables and constants declared in this way are invisible from other instruments.

Warning:

Whereas within the body of the procedure the compiler monitors the sequence order of blocks and the block var cannot be located after the block of commands introduced by the keyword begin, it is not so in the case of the body of the instrument. Thus, if there exist instrument local variables with a name identical to the global data element, then the order of declaration matters.

For example, the application

data

  var
    a : real { init_value = 1 };
  end_var;

end_data;
...
instrument

  meter m1;
    expression = ch1 / a;
    var
      a : real;
    end_var;
  end_meter;

end_instrument;

will work well at first startup, although during compilation of expression after the expression keyword the local variable a does not exist. Therefore, the global variable a initialized to 1 will be used for division in the expression. However, after automatic generation of the body of the meter instrument (e.g. after switching the development environment from graphic mode to text mode), the block of variables will be generated first

  meter m1;
    var
      a : real;
    end_var;
    expression = ch1 / a;
  end_meter;

and during next start the expression after the expression keyword will be compiled with another (local) variable. Moreover, this variable is initialized to the default value 0 and it will cause the application runtime fatal error during division. Therefore, if you write blocks of variables and constants in the text mode, write them first (the graphical editor always generates these blocks first).

What then is the process when searching for a data element of a certain name? The first step depends on the fact whether the data element is used in the expression inside the body of the instrument (e.g. expression = a + b for the meter instrument) or inside the instrument procedure. The next two steps are the same.

  1. First it is checked if there exists a local variable or procedure parameter of the given name. Parameters and local variables share one name space and that is why it is not possible to give the same name to a parameter and a procedure local variable. Of course, this rule will apply only to data elements used inside procedures.

  2. If an identifier is not found inside the procedure, it is checked whether it is not declared inside the instrument as a local variable or constant.

  3. Only after the identifier has not been found in the list of instrument local data is it searched for among global data elements.

  4. If the identifier has not been found even on the global level, the system announces a compilation error.

Take into consideration that only names of data elements are evaluated during compilation and their attributes (kind and type) are ascertained according to names. Even if a global and local data element are absolutely different in their kind and type, they must always be used in compliance with the lowest declaration. Consider the following example:

...

driver drv = vsource.dll, 'VSOURCE.DMF', 'VSOURCE.PAR'; end_driver;

data

  channel { driver = drv; init_value = 0 };
    a : real { direction = input; driver_index = 1 };
  end_channel;

end_data;

instrument

  meter m1;
    expression = a; (* a used as global channel of the type real *)
  end_meter;

  string_display s1;
    var
      a : string;
    end_var;
    expression = a; (* a used as a local variable of the type string, global channel a is not available *)

    procedure Go();
    var
      a : array [0..1] of boolean;
    begin
      ...
      if a[ 0 ] then (* a is the local array of variables of the type boolean *)
        ...
      end;
      ...
    end_procedure;

  end_string_display;

end_instrument;

Note that on the local level (of both the instrument and the procedure) only constants or variables can be declared. Channels, scheduled elements and other complex data element kinds can be declared on the global level only.

Like with local variables in the procedure, variables declared within the block var will always be initialized at the beginning of activation of the instrument, variables from the block static will be initialized only on application startup and then their content will be changed only by assignments irrespective of the activation (timing) of the instrument. If the value of the instrument variable changes “in a strange way” to a certain value at application runtime, make sure it is located in the block static and not in the block var.

Native procedures

There are many features of individual instruments, which can be represented as properties only with difficulty or inefficiently, even if they are expressed e.g. as an expression. These features of individual instruments are therefore made accessible as so-called native procedures.

Remark:

In older versions of Control Web and Control Panel, which did not allow definition of procedures within any instrument, OCL was implemented only in the form of the instrument program and native procedures were the only type of procedures in the system.

Both definition and implementation of native procedures is exclusively an issue of appropriate instrument class and has absolutely no influence on its binary compatibility with the rest of the system. Thus, instruments’ native procedures can be added any time, without compromising the backward compatibility. Only when an application program calls a procedure, which is not available (e.g. in an attempt to compile a more recent program using older versions of instrument DLLs etc.), the compilation error is announced and the program must be modified.

However, the convention allows only to add new native procedures and preserve names, types of arguments (signature), as well as semantics of the already existing native procedures. Keeping to this convention will ensure upward compatibility of all applications, meaning that the more recent version of the Control Web is able to compile all older application programs.

The typical example of using native procedures is panel visibility control. Each panel has the possibility to define the expression controlling its visibility.

visibility = condition_of_visibility;

However, should the panel permanently evaluate the visibility condition with periodical timing or after changing a data element stated in the expression, it is an absolutely inappropriate waste of time. That is why such panels are usually not timed but only activated by another instrument which changes the visibility condition. However, showing and hiding panels can be solved in a very smart manner by means of OCL native procedures. For instance it suffices to call native procedures to switch the visibility of two panels:

panel1.Hide();
panel2.Show();

Many properties are only available through native procedures. For example, a simple call

panel1.MoveTo( 100, 200 );

will move the instrument panel1 to coordinates 100, 200 of display pixels.

From the calling code point of view, the native procedures behave in the same manner as any other procedures, only they are not declared anywhere in the application source code. Each instrument only implements a proper set of native procedures. The list of native procedures, together with their description, can be found within the instrument palette described in Chapter Integrated development environment.

Therefore, there are three categories of procedures within the Control Web, which differ in some properties:

Category of the procedure implemented by user called from procedures called by the system
event procedures yes yes yes
user procedures yes yes no
native procedures no yes no

Kinds of procedures and their calling

Summary

OCL is a powerful tool helping wherever it is necessary to describe more complex behavior of the application. The basic OCL features are:

  • OCL is a general programming language, enabling notation of an arbitrary algorithm.

  • The whole OCL code is concentrated to procedures. Procedures can never occur independently; they are always defined within an instrument.

  • There is no “main procedure”. The Control Web is an event-driven system and procedures are primarily called as responses to the occurrence of some event.

  • The possibility to declare procedures is not restricted only to event procedures, it is also possible to define any quantity of user procedures in the application, however, other procedures in the application are responsible for their calling.

  • Instruments often implement native procedures, which cannot be declared but can be called.

  • Local data elements, invisible from other places, can be declared within procedures. Local data elements will make global data elements of the same name (provided they exist) invisible.

  • Procedures can have their own parameters, operating in the same way as local data, but their values are set by the calling code.

  • Procedure parameters can be passed in two — by value or by reference. The parameter passed by the value is evaluated and copied into the variable of the parameter, its changes will not be reflected outside the procedure. All changes in the parameter passed by reference will be reflected directly in the respective data element. It is not possible to pass constants and expressions by reference, only simple data elements.

  • It is possible to pass the whole array into the procedure by reference. The built-in functions loindex and hiindex enable finding of the lower and upper indexes of the passed array.

  • The procedure may return the value. Then, it is possible to call it as the function within the expression either in the body of another procedure or another arbitrary expression.

  • It is possible to declare local data elements within the instrument. These elements then can be used in notation of expressions in the instrument as well as in the procedures of the given instrument. It depends on the sequence order of declarations — local data elements must be declared before their use anywhere in the instrument. Local data elements are invisible from other instruments.

  • When deciding which data element of the same name is to be used, the most local always has priority: a variable in the procedure has priority against a variable in the instrument and a variable in the instrument has priority against a global variable.

  • It is not possible to declare other kinds of data elements than constants and variables at the local level.

  • The procedure is identified by its signature — name, types and sequence order of parameters and the type of return value. All numerical types are compatible (exchangeable) and coincide from the signature point of view.

  • Two procedures with identical signatures cannot be declared within one instrument. To avoid problems connected with distinguishing signatures, it is not possible to declare procedures with the same name as basic event procedures and differing only in parameters, even if their signatures are different.

  • The event procedure can be called after the incidence of the event only if its signature corresponds to a defined signature for the given event procedure.

 
 | About company | Products | Technical support | Software download | Documents download | 
Moravian Instruments, Masarykova 1148, Zlin 4, CZ-76302, Czech Republic