Expression Functions
Expressions are evaluated wherever a value is computed from tags — expression tags in the namespace, expression bindings on a property, transforms on a binding, and map-transform rows.
Expressions are quality-aware: every operand carries a quality, and a result carries the worst quality of its inputs. A computed value can never appear healthier than the data behind it.
A tag is referenced as tag["<path>"], with the full namespace path in quotes —
tag["Line1/Tank1/Level"]. A bare path such as Tank1/Level is not a tag reference; it parses as
a division.
Numeric functions
| Function | Signature | Returns |
|---|---|---|
abs | abs(x) | Absolute value |
min | min(a, b) | The smaller of two values |
max | max(a, b) | The larger of two values |
sqrt | sqrt(x) | Square root |
clamp | clamp(x, lo, hi) | x bounded to [lo, hi] |
clamp(tag["Tank1/Level"], 0, 100)
max(tag["Motor1/Current"], tag["Motor2/Current"])
sqrt(abs(tag["FT101/DeltaP"])) * tag["FT101/KFactor"]
abs, min and max keep whole numbers whole: given two integer tags they compute exactly and
return an integer. Mixing an integer with a decimal, or calling sqrt or clamp, works in decimals.
clamp takes the worst quality of all three operands — a bad limit makes the result bad, which is
the correct behaviour when a limit comes from a tag.
Bit manipulation
Industrial data packs many booleans into one integer — status words, alarm words, fault registers. These functions unpack them without a script.
| Function | Signature | Returns |
|---|---|---|
bitAnd | bitAnd(a, b) | Bitwise AND |
bitOr | bitOr(a, b) | Bitwise OR |
bitXor | bitXor(a, b) | Bitwise XOR |
bitNot | bitNot(a) | Bitwise NOT |
shiftLeft | shiftLeft(a, n) | a << n |
shiftRight | shiftRight(a, n) | a >> n |
getBit | getBit(value, pos) | Bit pos as a boolean |
setBit | setBit(value, pos, on) | value with bit pos set or cleared |
Bit positions are 0-based and valid for 0..63. An out-of-range position produces a bad-quality
result rather than a silently wrong one.
getBit(tag["PLC1/StatusWord"], 3) is fault bit 3 set?
bitAnd(tag["PLC1/AlarmWord"], 0x00FF) != 0 any alarm in the low byte?
setBit(tag["PLC1/CommandWord"], 5, true) build a command word
Integer operations are performed at 64-bit width, so a status word wider than 32 bits behaves
correctly. A shift count outside 0..63 is likewise an error rather than a silent wrap.
Time and duration
There is no separate time type. An instant is a whole number of nanoseconds since 1970 (UTC) —
the same as now — and a duration is a whole number of nanoseconds, so the two combine with
ordinary arithmetic.
| Function | Signature | Returns |
|---|---|---|
seconds · minutes · hours · days | minutes(n) | A duration of n whole units |
secondsBetween · minutesBetween · hoursBetween · daysBetween | hoursBetween(a, b) | The time from instant a to b, as a decimal count of units |
floorToSecond · floorToMinute · floorToHour · floorToDay | floorToHour(t) | t rounded down to the start of its second, minute, hour or day (UTC) |
yearOf · monthOf · dayOf | monthOf(t) | The calendar field of instant t, in UTC |
hourOf · minuteOf · secondOf | hourOf(t) | The clock field of instant t, in UTC |
weekdayOf | weekdayOf(t) | The day of the week, 0 = Sunday, in UTC |
now - tag["Pump1/LastRun"] > minutes(5) idle for more than five minutes?
hourOf(now) >= 6 && hourOf(now) < 14 inside the morning shift (UTC hours)
Every calendar function works in UTC, not the plant's time zone: hourOf(now) on a site at
UTC+7 reads 7 hours behind the wall clock.
Arithmetic and comparison
Standard operators are available: + - * / %, == != < <= > >=, && || !, and parentheses.
(tag["Motor1/Current"] / tag["Motor1/RatedCurrent"]) * 100
tag["Tank1/Level"] > 80 && tag["Pump1/Running"]
Quality propagation
| Situation | Result quality |
|---|---|
All inputs Good | Good |
Any input Uncertain | Uncertain |
Any input Bad or NotFound | Bad |
| Non-numeric input to a numeric function | Bad |
Bit position out of 0..63 | Bad |
This is why a widget bound to an expression can be relied upon to show bad quality: the expression layer does not launder it.
Where expressions appear
| Place | Notes |
|---|---|
| Expression tag | A tag whose value is an expression — computed once, then usable anywhere a tag is |
| Expression binding on a property | Computes the property's value from one or more tags |
| Expression transform on a binding | Reshapes a value the binding already fetched |
| Map transform — expression input | The row matches when the expression is truthy |
| Map transform — expression output | Computes the mapped output |
| Threshold transform | Band boundaries |
The three differ by where the value lives, which is the decision worth getting right. An expression tag is computed once in the namespace and can be trended, alarmed and reused anywhere. An expression binding computes a property's value on one screen. An expression transform reshapes a value some other binding already fetched.
If the same expression appears on a second screen, it wanted to be a tag.
→ Expression tags · Property bindings
When to reach for a script instead
Expressions are the right tool for arithmetic, bit unpacking and simple conditionals. Use a script transform or a Gateway script when you need:
- Iteration or data structures
- Database or historian access
- Multi-step logic with intermediate state
- Anything you would want to unit-test
An expression that needs a comment explaining it has usually outgrown being an expression.