Action Triggers and Script Blocks

As noted elsewhere, steady-state code runs once when the state is first opened, and then statements are triggered to run again when the value of a variable within that statement changes. The order of statements in steady-state code is of little importance.

But there are times when you need code to run in a predictable order; (first read a value, then remove spaces, then store the value, then read a new value...). You also need a way to move from being within one state (perhaps the "stopped" state) to another (perhaps the "running" state).

Both of these tasks are accomplished using the function If, which is an action trigger that can do any of:

  • Cause execution to move from one state to another. (For example, changing tabs in a tag configuration folder.)
  • Cause a script block to start execution. (See following topic.)
  • Execute the statements in a script block once, then move from one state to another.

Being a trigger, it depends on a conditional. The general format is as follows:

If some-expression-becomes-true NewState;
[
  X = X + 1;
]    

Note that the If function is called in steady-state and (like all VTScada statements) ends with a semicolon. The square brackets immediately following the If statement mark the beginning and end of a script block. In this example, code within the script block will execute exactly once, then control will transfer to NewState.

When combining conditionals in the action trigger, pay close attention to the rules of operator precedence to avoid unexpected results.

Script Blocks

Code within a script block is not event-driven. Statements here will always run in a predictable order. A change to a variable or parameter within a statement in a script block does not cause that statement to execute again. You have control over the order of script execution using functions such as DoWhile, IfThen and others.

While a script block is active, no instructions outside of the block (but within the same state) will execute. The block will repeat unless there is a state transfer in the action trigger or until the conditional trigger becomes false. Between repetitions, other statements in the state have a chance to run.

Examples:

Change state without running a script block:

If some-expression-becomes-true NewState;   

In this example, there is no script block. When the conditional expression becomes true, control moves from the current state to the named destination state.

Run a script block without changing state.

If X < 10;
[
  X = X + 1;
]    

In this example, there is only a script block. Code within the block will execute again and again for as long as the action-trigger expression remains true. When writing an action trigger that does not transfer to another state, use great care to ensure that the conditional will change back to false. VTScada includes a set of automatically-resetting functions for use in this situation. For example, if you require a script block that will run a single time without transferring to another state, a common technique is to use the Watch() function as the trigger, with an initial value of 1. Watch(1) resets automatically, meaning that it will change to false after triggering within an If statement.

If Watch(1);
[
 { ...script code to be run once... }
]

Do both.

If 1 Main;
[
X = 10; ]

Variations on this example are found throughout VTScada. The script block is guaranteed to run because the conditional is TRUE. But, the script block runs only once because there is a branch to another state in the action trigger.

Example: An Initialization State

A common feature of VTScada modules is to have an initialization state that sets values before handing control to a main state that does the work. In the following example, the initialization state obtains the current time for some purpose needed by the main state.

[ { variable declarations }
  TS { A time stamp, set upon startup };
]


Init [ 
  If 1 Main;
  [
    TS = CurrentTime();
  ]
]


Main [
  { ... code to do something with the start time ... }
]

Note the statement, "If 1 Main;". "If TRUE" is not something you would expect to see in most programming languages, but it makes perfect sense here. The If function is required because it is the best1 available trigger to run a script block. The TRUE (i.e. 1) is required because the script block must run as soon as the Init state starts. This example is guaranteed to execute the code in the script block once and only once before moving from the initialization state to the main state.

 

When writing an initialization state such as the previous example, put all of your code into the script block. As soon as the action trigger happens, the script block will run and then control will transfer to the named state. Other steady state statements in the initialization state will stop, therefore there was no point to starting them in the first place. Any following the If are not guaranteed to run at all.

Race Conditions

It is important to watch for possible race conditions. For example, if a state contains two action trigger statements with identical conditionals but different actions:

If HighAlarm Shutdown;
If HighAlarm StartPump;

Both of these actions change to a new state on the same condition. It cannot be predicted which will execute first, and the final state will be either Shutdown or StartPump. This is called a "race condition", because the module depends on which action trigger wins the race.

Functions and Script Blocks

When selecting functions for use in a script block, take note of the "usage" description.

Example from the function reference.

If the usage is described as "steady-state only", it cannot be used in a script block. Note that VTScada contains a number of standard programming functions for control of execution within a script block, such as IfThen, WhileLoop, Case, etc.

Tab-like buttons

In this example, you will create two buttons that behave like a tabbed dialog. With each button press, a different message is displayed. Note that this example has identical code, repeated in several states. That's usually not considered good practice, but for the sake of the exercise we'll let it pass. Use copy-and-paste to save time.

  1. If you have the Script1 application from an earlier example, continue working with it, removing all the code you added earlier. Otherwise, create a new script application.
  2. Use a text editor to open the application's AppRoot.SRC file.
  3. Edit the file as follows:
{================================= System ====================================}
{=============================================================================}
(
  System                       { Provides access to system library functions };
  Layer                        { Provides access to the application layer    };
)
[
  Graphics               Module { Contains user graphics                     };
  WinTitle = "User Application" { Window title                               };
  RunningOnVIC                  { TRUE if this is a VIC session              };
]

Init [
  If 1 Main;
  [
    RunningOnVIC = IsVICSession();
  ]
]

Main [
  Window(  0,   0           { Upper left corner   },
         800, 600           { View area           },
         800, 600           { Virtual area        },
         Graphics()         { Start user graphics },
          {65432109876543210}
         0b00010000000110011,
         Concat(WinTitle, RunningOnVIC ? " - %S" : ""),
         0, 1);
]

<
{============================= System\Graphics ===============================}
{ This module handles all of the graphics for the application                 }
{=============================================================================}
Graphics
[
  Current = 0                                      { Selected message         };
]
Switch [
   { Display the selected message } 
  If Current == 0 Message0; 
  If Current == 1 Message1;
]


Message0 [
   { Create a background for the message }
  ZBar(50, 550, 750, 50, 30 );
   { Display the message } 
  ZText(250, 300, "This is the first message", 0, 0);

   { On button press, switch the message            } 
  If WinButton(200, 100, 275, 80, 0, "Message 0", 1, 0) Switch;
  [
    Current = 0;
  ] 
  If WinButton(300, 100 ,375, 80, 0, "Message 1", 1, 0) Switch;
  [
    Current = 1;
  ]
]

Message1 [
   { Create a background for the message }
  ZBar(50, 550, 750, 50, "<FFAADDFF>");
   { Display the message                 } 
  ZText(250, 300, "This is the second message", "<FF000000>", 0);
  
  { On button press, switch the message            } 
  If WinButton(200, 100, 275, 80, 0, "Message 0", 1, 0) Switch;
  [
    Current = 0;
  ] 
  If WinButton(300, 100, 375, 80, 0, "Message 1", 1, 0) Switch;
  [
    Current = 1;
  ]
]

{ End of System\Graphics }
>