Expression Bindings: Driving SCADA Screen Properties From Live Data
Introduction
Traditional HMI screens hardcode every property: the label says "Tank 1", the color is hardcoded green, the tooltip is a static string. If the value, the equipment state, or the user's role should change what operators see, the engineer has to wire up a separate animation, add a script, or build a whole alternate screen. It's tedious, and the result is rarely as polished as it could be.
Expression bindings solve this by letting any text, color, or tooltip property on a screen be a live formula — like a spreadsheet cell that recalculates whenever an input tag changes.
What is an Expression Binding?
An expression binding is a small formula that starts with an equals sign (=) — following the Excel convention. When the runtime displays the property, it evaluates the formula against live tag values and uses the result.
Examples:
| Property | Binding | Result |
|---|---|---|
| Label | Tank 1 | static |
| Label | ="Tank " + Tank_Number | "Tank 1", "Tank 2", etc. |
| Tooltip | =Pump_Running ? "Running since " + format(Start_Time) : "Stopped" | state-aware |
| Text color | =Temperature > 80 ? "red" : "green" | threshold indicator |
| Visibility | =Maintenance_Mode == false | hide during maintenance |
The same screen object now carries its own display logic without a single script.
Core Syntax
The syntax is deliberately close to what anyone familiar with Excel formulas already knows:
- Tags:
Temperature,Pump_Running. For tag names containing spaces, use brackets:[Flow Meter 1]. - Literals: numbers (
42,3.14), strings ('red'or"red"), booleans (true/false),null. - Arithmetic:
+ - * / %. - Comparison:
> < >= <= == !=. - Logic:
&& || !. - Ternary:
condition ? a : b. - Strings concatenate with
+:"Level: " + Level + "%".
Transform Functions
Expression engines shine when you have a library of reusable transforms. These are some of the most common:
Value scaling
```
=scale(RawPressure, 0, 4095, 0, 10)
```
Maps a raw 12-bit ADC reading (0–4095) to engineering units (0–10 bar). A one-liner replaces what used to require a scaled tag or a pre-processing script.
Clamping
```
=clamp(MotorSpeed, 0, 1500)
```
Guarantees the displayed value stays within a sensible range even if the PLC sends a bogus reading.
State mapping
```
=map(MachineState, 0, 'Idle', 1, 'Running', 2, 'Fault', 3, 'Maintenance', '?')
```
Turns an enum integer into a human-readable label. The final argument is the default when no key matches.
Formatting
```
="Flow: " + format(Flow, 1) + " L/min"
```
format(n, digits) rounds and pads to a fixed decimal place, which is almost always what an operator wants to see on-screen.
Time
```
="Last updated: " + time()
```
now(), today(), time(), and date() let you stamp displays without a dedicated timer tag.
Real-World Examples
1. Equipment status card that changes color
- Label binding:
=Pump_Name + " — " + map(Pump_State, 0, "Off", 1, "Running", 2, "Fault", "?") - Text color binding:
=Pump_State == 2 ? "#ef4444" : Pump_State == 1 ? "#22c55e" : "#6b7280"
One object on the canvas now serves every pump in the plant when duplicated and rebound to the appropriate tags.
2. Batch progress tooltip
- Tooltip:
="Step " + Current_Step + " of " + Total_Steps + " — " + format(Progress_Pct, 0) + "% complete"
Hover over the batch icon and get the full breakdown without opening a faceplate.
3. Security-aware button label
- Label:
=User_Role == "viewer" ? "View Recipe" : "Edit Recipe"
The button's behavior is gated by role on the backend, but the label reflects what the user can actually do — no confused clicks.
Safety and Sandboxing
Any time you let users type formulas into a production control system, the implementation has to be airtight:
- No
eval(), nonew Function()— a recursive-descent parser walks a whitelisted grammar. The output is a validated AST, not arbitrary JavaScript. - No access to the global environment — functions that exist are only the built-ins the engine exposes. There is no way to access
process,require, file paths, or network APIs from a binding. - Type coercion is explicit — comparing a string to a number produces
false, not an exception. Bad bindings never crash the runtime; they simply display an error indicator next to the broken object. - AST caching — each unique source string is parsed once; subsequent evaluations reuse the cached tree. Performance stays flat even with hundreds of bound properties on a single screen.
When to Use Bindings vs. Scripts
Expression bindings are the right tool when the logic is:
- Declarative — "this label is always the product of X and Y"
- Small — fits on one or two lines
- Read-only — bindings transform values for display; they don't write back
Reach for scripts when the logic requires:
- Loops, accumulators, or state that persists across ticks
- Side effects (writing tags, sending notifications, calling APIs)
- Complex branching that would be unreadable as a ternary chain
A healthy SCADA codebase uses both — bindings for the 80% of cases where "just show me a value" is enough, scripts for the 20% where orchestration is needed.
Conclusion
Expression bindings close the gap between static HMI screens and live process data. By treating any text, color, or tooltip as a small formula against the tag model, engineers eliminate dozens of one-off animations and scripts. The result is screens that feel less like paint-by-numbers diagrams and more like living representations of the plant.
OptiZeus implements expression bindings on every text property in the screen builder — labels, tooltips, popup titles, action URLs, and confirmation messages. The same syntax also powers the Query Table component's parameter bindings, where each :param can be driven by a tag reference that updates in real time. Bracket notation for tag names with spaces, a dozen built-in math and string transforms, live preview in the editor, and AST caching come standard.