Latching and Resetting Functions

All functions and statements are reset when the state that contains them becomes active. Some functions that contain an "enable" parameter will reset when that parameter becomes true. For example, the AbsTime() function will not start counting time until its enable parameter becomes true.

Some functions can reset themselves. For example Latch(). The second parameter to this function is a "reset", which when true, resets the function for the next repetition. Any functions or expressions used for the two parameters to Latch() are also reset.

 

VTScada includes a class of functions referred to as the "automatically resetting functions". These are designed such that their value will latch when set, and will not change with future parameter changes. AbsTime() is an example - after the designated time has arrived and the function becomes true, it will remain true until reset.

The automatically-resetting functions will reset when used as the action trigger of an If statement. For example, in the case of MatchKeys, an action trigger might watch for the operator input of a specific single key. If the intent of such an action is to perform an action once, such as increasing a set point by a fixed amount, then the action trigger must be false before the next time it is tested or else the script will be executed innumerable times for just a single keystroke. Because the MatchKeys function is automatically reset when the If statement containing it becomes true, the action script will execute only once when the key is hit.

You can design your own subroutines to be automatically-resetting or not. The difference is in the use of the Reset() function within the subroutine.

Examples:

LightOn = AbsTime(1, 10, 0)

When the next time interval arrives (any multiple of 10 seconds in this example), the light goes on and stays on.

If AbsTime(1, 1, 0);
[
  LightOn = !LightOn;
]

The light will switch on and off every second since the If statement resets the AbsTime function every time it becomes true. One second later, it becomes true again.

LightOn = Latch(AbsTime(1, 2, 0), AbsTime(1, 2, 1))

This is functionally equivalent to the previous example. The light flashes on and off each second. Note that a Latch can be used in a Calculation tag expression, where an If cannot.

If TimeOut(1, 1);
[
  J++;
]

J will increment once per second, since the Timeout is reset each time it becomes true.

Z = TimeOut(1, 1);
If Z;
[
  J++;
]

J will increment continuously, as fast as your computer will allow.

VTScada Functions That Are Reset Automatically:

TimeOut versus RTimeOut

The timeout functions are good examples of automatically resetting functions. These two are so similar that it's easy to choose the wrong one in any given situation.

  • TimeOut returns true when the uninterrupted time that an expression is true reaches the specified value.

  • RTimeOut is a cumulative timer. This function returns true when the total time that an expression is true reaches the specified value.

The following exercise will illustrate the difference.

  1. Create a new script application.

For the first example you will use RTimeOut, which counts the cumulative time that its enable parameter is true.

  1. Edit the Graphics submodule in the application's AppRoot.SRC file as follows:
{========================== System\Graphics =============================}
{ This module handles all of the graphics for the application            }
{========================================================================}
Graphics
[
  StartTS                           { timestamp upon beginning           };
  NowTime                           { updated time while running         };
  RunClock = 1                      { Boolean to enable timer            };
]
Init [ If Watch(1) Main;
  [
    NowTime = StartTS = CurrentTime(); 
  ]
]
Main [
  ZText(100, 100, Concat("Elapsed time: ", NowTime - StartTS), "<FFFFFFFF>", 0);
  ZText(100, 120, Concat("RunClock is ", RunClock), "<FFFFFFFF>", 0); 
  If AbsTime(1, 2, 0);
  [
    RunClock = !RunClock;
  ] 
  If RTimeOut(RunClock, 4);
  [
    NowTime = CurrentTime();
  ]
]
{ End of System\Graphics }
>
  1. Import file changes and run the application. Because RunClock is FALSE for two seconds out of four, the elapsed timer should update approximately every eight seconds. The fractional milliseconds difference shows the time it takes for code to run.
  1. Stop the application.
  2. Change RTimeOut to TimeOut and save the file.
  3. Import file changes and run the application again. The message will never update. For the TimeOut to trigger, its enable parameter needs to remain true for an uninterrupted time span.
  4. Stop the application.
  5. Replace the first parameter in TimeOut with 1.
  6. Import file changes and run the application again. The message will change every 4 seconds.

 

Triggers and States

In this exercise, you will build a simple counter that will say "All done" using a fresh state when the counter reaches the specified value. The point of the exercise is that you will need to look up the functions to see what they do and how to use them instead of simply copying code.

The answer code for this application is shown at the end of this topic. Try to avoid looking at it until you have built your own application. You will need to refer to the function reference in the VTScada documentation.

  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. Write a ZText statement to place the word "Value: " in the center of the screen. Set the color to green and use 0 for the font.
  4. Add a variable, X, to the Graphics module, initializing its value to 0. (zero)
  5. Modify the ZText statement to use a Concat function, displaying the value of X after the word "Value: ".
  6. Add an action trigger (If statement) that triggers every second using the TimeOut function.
  7. Add a script block [ ] after the action trigger.
  8. Within the script block, increment the value of X by 1. Run the application to make sure that it works.
  9. Add a WinButton, placing it near the upper right of the window and giving it the label "Reset".
  10. Use the WinButton as a trigger to reset the value of X to zero when pressed.
  11. Again, run the application to make sure that it works.
  12. Add a second variable, MaxCount, which is initialized to 20.
  13. Add an Edit field below the reset button. This should be coded so that the user can change the value of MaxCount.
  14. Create a new state named Done. The Done state should display an "All done!" message to the user.
  15. Create a new trigger so that when X reaches MaxCount, the module will transition to the Done state.
  16. Run the application.
  17. If you coded the reset using the most direct method, there may be a flaw. Try the button a few times to see it. Notice if the transition from 0 to 1 takes less than a second after any given reset. If so, that's the flaw. The TimeOut is unaffected by the reset and will trigger one second after the last TimeOut, regardless of when you press the reset button.
    Fix the flaw. As a hint, think states.

Application for the Trigger and States example.

{================================= 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" : ""),
         "<FFAAAAAA>", 1);
]

<
{============================= System\Graphics ===============================}
{ This module handles all of the graphics for the application                 }
{=============================================================================}
Graphics
[
  X                                              { Counter                  };
  MaxCount = 20                                  { Maximum count            };
]

Init [ 
  If 1 Main;
  [
    X = 0;
  ]
]

Main [
  ZText(350, 300, Concat("Value: ", X), "<FF007000>", 0) { Display the count};

   { Every second... } 
  If TimeOut(1, 1);
  [
     { Increment X }
    X++;                    
  ]
   { Reset the count }  
  If WinButton(700, 100, 750, 80, 0,"Reset", 1, 0) Init;

   { Allow user changes }
  System.Edit(700, 130, 750, 110, "Max", MaxCount, 1);

   { Time to quit } 
  If X >= MaxCount Done;
]

Done [
   { Display the count                                                      }
  ZText(350, 300, "All done!", "<FF000070>", 0);
]
{ End of System\Graphics }
>

Advanced Topic: Create Your Own Automatically-Resetting Functions.

A function is a subroutine module, defined as any module that is launched from within a script block and which contains a Return() function. Note that the location of the Return() function within the subroutine makes a significant difference to the behavior. If the following conditions are met, then the subroutine will automatically reset:

  • Return() is located in a script block of the subroutine.
  • The action trigger for that script block includes a transfer to another state. The destination state typically contains no code.
  • The subroutine is used as the conditional to an If statement,

Example: An automatically resetting subroutine

In the following, Check always returns 1. However, this does not create an If 1 condition in the first state since the subroutine resets automatically. The return is within a script block, and the action trigger for that script block includes a transfer to another state.

Main [
  If Check();
  [
    I++;
  ]
]

<
{======================= Check ==============================}
Check

Init [
  If 1 Main;
  [
    Return(1);
  ]
]

Main [
]
>

In this example, Check() - a user-created function - is functionally equivalent to Watch(1).