Examples for Functions and Operators

Boolean. Noun, always capitalized. A binary variable having two possible values, either true or false. Named for George Boole.

You will often want to choose between actions based on the current situation. For example, if the room is too cold, increase the heat, otherwise reduce it. If the system is down for maintenance, disable the alarms, otherwise leave them enabled.

The function to do this is IfElse and there are two ways to write it:

IfElse(Conditional-Expression, Expression if TRUE, Expression if FALSE)

Conditional-Expression ? Expression if TRUE : Expression if FALSE

Functionally, these are identical. It's your choice as to which form you prefer to use.

Any expression that will return either a 0 (FALSE) or 1 (TRUE), can be considered to be a test expression. (Any numeric other than zero is considered TRUE, but 1 is typically used by convention).

Typically, test expressions use conditional operators such as:

> Greater than
>= Greater than or equal to
< Less than
<= Less than or equal to
== Equal to
!= Not equal to

 

Situational Message or Instructions

You can use the MultiText widget to display a message that varies according to a changing value. But, what if it also makes a difference whether equipment is running or an alarm is active? Further, what if you want the text of the message to include data calculated from two or more other tags? For this, you're going to need an expression.

  1. Open the Idea Studio.
  2. Working on the Overview or Station Status page, draw text (plain text from the ribbon, not a text widget) on the page.
  3. Open the Edit Text dialog and change the data source to Expression.

  4. Create an expression so that when the Level value is more than 50%, the message is "Level exceeds safe amount". When the Level is less than 50% the message should be "Safe level".
  5. [Optional] Adjust the message to tell your operators the percentage by which the level exceeds the safe amount.

If you knew the math behind the system, you could create a message that tells operators how to adjust the pump or valve to balance the flow rate, or predict when the tank will empty or fill.

Keep the Else's under control !

Writing long "If Else - Else - Else ... " expressions is not efficient and can be avoided. To prevent excessively nested expressions, the property MaxNestedExpressionDepth limits you to 200 function calls (including IfElse) within a single expression. There is no need to test this limit, regardless of the complexity of your application.

Alternatives to nested IfElse structures:

A common task is to build a string based on the current value of a tag (or several tags). For this example, suppose that you need to build a message or address that varies with a tag's current state. A long and inefficient method might be to write:

[SomeTagName] == 0 ? "Message or Address 0"
                   :
[SomeTagName] == 1 ? "Message or Address 1"
                   :
[SomeTagName] == 2 ? "Message or Address 2" 
                   :
 etc. for 100 or more messages...

There are alternatives:

Option 1

A much easier method is to use a CASE statement in steady state. All statements will execute, but only the result of the statement matching the requested index will be returned.

X = Case([SomeTagName],
{ 0 } "Message or Address 0", { 1 } "Message or Address 1", { 2 } "Message or Address 2", { etc });

Option 2

Another option is somewhat more complicated to create, but has the benefit that the messages are stored in application properties instead of code and therefore can be added to or edited by anyone with the Configuration privilege. It is not necessary to write more code when messages change or new messages are created.

1. Create a set of application properties for each situation:

 Situation0         = Message or Address 0 
 Situation1         = Message or Address 1
 Situation2         = Message or Address 2
   (etc.)

2. Write an expression that uses the Variable function to concatenate the tag value onto the prefix "Situation" to return the appropriate property value.

 Variable(Concat("Situation",[<SomeTagName1>]))

No comparisons or If-Else statements are required. This single line of code can return any message that you have created. For more complicated situations, use Concat to build the appropriate property name or combine several values into one message or address.

 

Time functions

Several functions are available to help you find the current time. These include:

Now(interval) Returns the number of seconds elapsed so far today, updated every (interval) seconds.
Time(seconds, format) Formats the number of seconds since midnight into a form that's easier for a human to read and understand.

Several other time-related functions are also available, but many cannot be used in a typical steady-state expression. For example, CurrentTime() will return the number of seconds elapsed since Jan 1, 1970, but it is a script-only function.

 

Having the number of elapsed seconds since a given reference point is useful to a computer, but not easy for humans to read. The Time() function is available to turn those seconds into a more friendly form. To use it, you will need to refer to a table of available formats to select the one you want.

In general, the Time() function looks like so: Time( Timestamp, Format Flag)

To obtain the current time, you could use any of the following (examples assume that it's 9:35 p.m.)

Time( Now(1), 2) --->   21:35:00

Time( Now(1), 7) --->   09:35 PM

The formatting codes and formatting strings can be found in the VTScada documentation. Part of this exercise is to practice finding them.

Date Functions

Similar to the time functions are those available for obtaining the date. Note that computers store both time and date as a "timestamp", which is defined as the number of seconds elapsed since January 1, 1970. (aka "the Unix epoch".)

One function to retrieve the current date is Today(). Much like the Now() function, the default format will be better suited to a computer than a human since it returns the number of full days elapsed since Jan. 1, 1970.

Having obtained the current date using Today(), you can then format it using the Date() function. This works much like the Time() function. Again, part of the exercise is to practice looking up the format codes and strings in the documentation.

Examples:

Assuming that today is Monday, Aug 13th, 2012, then:

Date(Today(), 3)

... yields, "08-13-12"

Date(Today(), "dddd MMM dd, yyyy") 

... yields, "Monday Aug 13, 2012"

Time and Date

Preparation: Create a Calculation tag and use its expression editor for the following:

 

  1. Enter the following expressions into the expression editor, closing the editor after each to see the result:

Now(1)

Now(5)

Time( Now(2), 2)

Today()

Date( Today(), 2)

Text Functions

In the function list of the VTScada documentation, text functions are found in the group "String and Buffer" (the terms that programmers use to say "text"). When browsing through the functions in this group, note that many are designed to work only in script mode, not in steady-state. Here are a few examples of text functions that you can use in your expressions.

Concat

One of the most frequently used functions is Concat(). This combines several bits of text together into one sentence. If any of the parameters is a number, it will be converted to text for use in the sentence.

Concat() can take any number of parameters. Don't forget to include spaces unless you want to join parameters together into a single word. Parameters may be expressions. Numeric values will be translated into strings.

Concat("This ", "and ", 3/4, " of that.")

returns "This and 0.75 of that."

As of version 12, you should create \ParmPhrase structures rather then use Concat to generate all user-interface text.

StrLen

Returns the length of a text string measured as the number of bytes. This can be useful to know when you need to find some portion of text.

StrLen("Welcome to VTScada")

will return 18.

SubStr

Returns a portion of a text string, starting at a specified number of characters from the left. Be careful: counting starts at zero.

Providing the length (number of characters) to return is optional. If you don't, you'll get everything from the nth character to the end.

This function has the form:

SubStr(String, Start[, Length])

Those square brackets have a meaning: they enclose parameters that are optional. Do not type the square brackets when writing a function in your expressions.

SubStr("Good morning to you.", 5)

Will return, "morning to you."

SubStr("Good morning to you.", 5, 7)

Will return, "morning"

Locate

This function locates a substring within a longer line of text, returning the location of that substring as the number of bytes from the beginning. Counting begins at zero, therefore if the substring is at the beginning of the longer string, 0 is returned. If no match is found, then -1 is returned.

You can specify a start point, which is useful if the substring occurs several times and you want to find all of them. You just have to run the function several times, starting one character to the right of each successive match.

You can also specify whether you want a case sensitive match or not. (Case sensitive is the default.)

Locate("Big haystack with a needle", 0, "needle")

returns 20

Locate("Big haystack with a needle", 0, "Needle")

returns -1

Locate("Big haystack with a needle", 0, "Needle", 1)

returns 20

Practice with text expressions

Typically, someone will want to use a text function to specify a given tag name on the fly within an expression. Something like...

Concat("[AlarmPriority", x, "]\Description")

...to retrieve the description of a given Alarm Priority tag. This doesn't work.

The expression will return the tag name and stop at that point, not continuing on to evaluate that tag's properties.

Fortunately, there's another way to specify the name of a tag. You can use the Scope() function, which takes any expression that evaluates to a tag name. So, for example,

Scope(VTSDB, "AlarmPriority0")\Description

The quotation marks are around the tag name to tell VTScada that this is text, not the name of a variable. If you use a function that returns text (like Concat does), you don't need to wrap extra quotation marks around it. It's text.

Concat("AlarmPriority", x)

Assuming that x is a variable containing a number from 0 to 4, this will return the name of an AlarmPriority tag as text, which is what the LocalScope function needs for a parameter.

However, there's still a problem... What you'll get won't be the description of the tag, it will be the phrase identifier key for the description. You'll need to do a look-up on that key to get the description by calling \GetPhrase(). (Note the backslash in front of \GetPhrase.)

  1. Create an I/O tag named X using the Discrete data type and with a Scaled Process Data Max set to 4. No I/O device or I/O address will be used.
  2. Draw X on the Overview page as a Numeric Entry widget.
  3. Create another I/O tag using the String Calculation data type.
  4. Give it an expression that returns the description of any specified Alarm Priority tag.
    Your expression should use [X], created in step 1, to get the priority number.
  5. Draw the tag on the Overview page using a Draw Text widget, placing it near the Numeric Entry widget.
  6. Switch to operator mode and put numbers 0 through 4 in the numeric input widget.

Timestamps

A VTScada timestamp is the number of seconds from January 1, 1970. You could obtain one right now if you could run the CurrentTime() function in a Calculation tag expression. (Which you can't.)

Sooner or later, you will want to translate a timestamp into a human-readable date and time. Or, you might want to generate a timestamp for an arbitrary moment in time. Or, most likely of all, you will want something that tells you when x minutes have elapsed since a given timestamp, such as the last good poll from a Polling Driver.

Given that...

  • Time stamps are measured in seconds since January 1, 1970.
  • There are 86400 seconds in a day, therefore Time()*86400 will give you the number of seconds from January 1, 1970 to midnight last night.
  • Now(n) will give you the number of seconds from midnight to now, updating at the rate of n seconds.

Combining that information, you can use the following expression to obtain a timestamp that updates once each minute:

Today()*86400 + Now(60)

Turn a timestamp into a human-friendly date and time

To find the number of days in a timestamp, you need to know how many times 86400 goes evenly into the number. Simple division, truncating to the nearest integer will give you that.

Int(SomeTimestamp/86400)

equals days since Jan 1, 1970. This is the same thing that the Today() function returns.

You also need to find out how many seconds remain for the portion of time measured into the day. You can do that with the modulus division operator, %. The modulus is the remainder. For example...

Let's say that you want to find the number of times that 2 goes into 7.5 evenly, and you also want to know the remainder. You could do it the long way...

  • 7.5 / 2 = 3.75.
  • Take the integer portion, int(), to get 3.
  • Then, 7.5 - (3 * 2) = 1.5 for the remainder.

Or you could do it this way:

  • int(7.5 / 2) = 3 for the number of multiples.
  • 7.5 % 2 = 1.5 for the remainder.

When was this exercise written?

Convert the following timestamp, created while this text was being written, into a human-readable form.

1490801880

hints...

  • After you have the number of days, use the Date() function to format it. I suggest format 4.
  • After you have the number of remaining seconds into the day, use the Time() function with that. Format 6 would be nice.
  • You'll need to combine those two. Use the Concat() function, and don't forget to include space between the date and the time.

A trigger for code

The AbsTime() function will return a TRUE value when a given time interval has passed. This is a handy feature if you want to schedule an event to occur every hour on the hour, or perhaps every morning. Note that, when used in an expression (as opposed to a VTScada script module), the AbsTime function does not reset itself to false.

The format for the AbsTime() function is:

AbsTime(Enable, Interval, Offset).

  • The first parameter, Enable, allows you to switch the function on and off. If Enable evaluates to FALSE, then the AbsTime function will be switched off. Otherwise, it will be on.
  • The second parameter, Interval, gives the time in seconds to wait between events. Note that this is an absolute time, not an elapsed time. (Hence the name of the function.)
    If you want an expression to run every 24 hours, you would set the interval to 86400. If hourly, set the interval to 3600. In any case, the interval must be greater than 0.
  • The third parameter is an offset, measured in seconds to wait after the interval has been reached. If you want your expression to run at five minutes past the hour, every hour, then you would set the interval to 3600 and the offset to 300.

Resetting functions in expressions

AbsTime (and other triggering functions) would be much more useful in expressions if you could make them reset automatically. Fortunately, there's an way to do that: Enclose the trigger within a Latch() function.

The Latch() takes two parameters. When the first parameter becomes TRUE, the overall Latch function becomes TRUE. When the second parameter becomes TRUE, the overall Latch function, including both parameters, resets to become FALSE.

Example:

Latch( AbsTime(1, 10, 0), AbsTime(1, 10, 5) )

On any ten-second mark (0, 10, 20, 30 ...) the first parameter will become TRUE. Latch will become TRUE. On any ten-second mark, offset by five seconds (5, 15, 25, 35 ...) the second parameter will become TRUE. This will reset the Latch to FALSE. The result is a metronome of sorts, toggling on and off every five seconds.

If using an expression that sets opacity to zero, it may be helpful to also check whether you're viewing that object in the Idea Studio or in the operator view. Do so with ParentWindow()\Editing For example:
ParentWindow()\Editing || Latch(AbsTime(1, 1, 0), AbsTime(1, 1, .5))

Strobe Light

  1. Draw a filled circle on a page.
  2. In the properties dialogue for that circle, open the Opacity option.
  3. Use an expression similar to the earlier example that will make the opacity toggle between true and false.

Noticing when a value changes - Watch()

A very common situation is that an expression needs to react when a change occurs. The "log on change" feature of the logger tag is an example of this at work.

The Watch() function provides you with a means to do this in your calculation tags. The format is as follows:

Watch( InitialValue, Parameter1, …)

You can watch as many parameters as you need. The InitialValue is the only mandatory parameter, and is commonly set to either 1 or 0 to specify the Watch function's initial state. For most expressions outside of a VTScada script code module, the initial value is typically set to 0.

Whenever any of the watched values change, the Watch() function will return True.

It's important to understand that there is no built-in deadband for specifying that a value must change by a certain amount before a Watch() is triggered. Even the smallest change will count.

Note that, as seen with the AbsTime function, the value does not automatically re-set back to its initial value when used in an expression. The Watch() function is best used in combination with the Latch() function, described earlier.

The Watch() function makes a very useful trigger, as it allows you to monitor any other tag (or expression, or ...) anywhere in your application.