Best practice defining logical functions in large scalable systems?

Replying the the OP.

I very rarely use map transforms given they are by nature always going to be “magic expressions” – you cannot centrally define them, and to me this is a massive design flaw to use them.

Past Experience

In the past, I have created expression tags in my device UDTs which map key enum tags like “status“ and “mode” tags from integer values into their descriptive names like “Stopped”, “Running”, “Opened”, “Closed”, etc.

Then I would have dataset tags for each of these key tags (Status / Mode) that stores a lookup of the descriptive name to the corresponding style class to use for representing it.

e.g.

The style classes themselves would reference CSS colour variables within the CSS props needed (background-color, color, fill, etc.) and have animcations on them where needed (e.g. “Starting” pulses the colour).
As an aside, this (animations) is one important reason that you should create style classes instead of just referencing CSS colour variables within the GUI props themselves.

Then in the GUI I would use expression bindings with:

lookup({System/Datasets/Style Class Maps/Device Status}, {<devices status tag value>}, "INVALID", "Value", "StyleClass")

This worked really well as it:

  • centralised and named the colour definitions for device colours within CSS variables

  • centralised and named the styles to use for different device states, which may include animatations where needed

  • centralised the mapping of key tag integer values into their corresponding descriptive text

  • defined and uses a standard set of these descriptive text and associating them to a corresponding styling

  • provided plc-agnostic configuration for PLCs where you cannot control the standards employed. e.g. Pump status enum might be non-standard 0-Running, 1-Stopped instead of 1-Running, 0-Stopped.
    This was one of the main drivers for me to go down this path to begin with

  • expression tag values I’m told are not translatable, so won’t work for projects requiring such
    I’ve just tested this however and I don’t see this being an issue @pturmel ?


    Added translation for Africaans: Manual → Mani. Forced project locale to be Africaans.

    In the client it translates ok:

    image

Where this can be improved upon:

  • expression tags update regardless if they’re displayed on a client or not. The higher the count of UDT instances in the gateway, the higher the number of unecessary updates.

However, despite this, I have a gateway with a split architecture (frontend/backend) running like like this that has over 600,000 total tags (~300,000 opc tags) where the CPU sits around 5% on the backend. The server is has 8 cores and 18GB RAM to Ignition, so it’s not actually that high spec’d.

The (possible) Future

After speaking to Phil however, where I will go with this will be to instead move the lookup and translation/mapping logic from the UDT instance level into the GUI to avoid unecessary expression tag updates.

With the above in mind, the full stack will be something like this:

  • Define colour variables in CSS via theme files or the adv stylesheet. Unless these colours need to change based on theme, they’re ok and more accessible to put them in the stylesheet. If they need to change however, then they obviously need to be in the theme files
  • Define Perspective Style Classes that reference your colour variables. You will likely have multiple sets of these which define the colours in different properties, like fill, background-color, color etc.for different purposes like: labels, symbols, icons, etc.
  • Define a document tag to store, for each device type (e.g. Motor, Valve, etc. – allow for different versions) and tag that needs it (e.g. Status, Mode, etc.) a map of the integer state of the tag to its:
    • descriptive, translatable text e.g. 2 => “Running”, 0 => “Stopped”, etc.
    • style class to use (most likely this will be a dict that defines the style class for the different usages e.g. Symbol, Label, Icon, Stroke, etc.
  • In your device UDTs, add a UDT parameter “deviceEnumType” which will define which styleclasses to use.
  • In your GUI, you can then use an expression binding to bind to the tag (using a direct tagpath, not the tag function) and reference the descriptive text and the style class to use, based on the device type from the tag itself and the value of the tag.

In practice, something like this (this is a concept at this stage that I have working):

Style Classes:

Tag: System/Styling/Device Type Enums (document type)

(colourVar probably shouldn’t be there)

Value:

Tag: UDT Instance

Using it in the GUI

For labels:

For the pump symbol:

PS. Ignore the relative parent-referencing used in these expressions! This is just a jerry-rigged test and I would never recommend using parent referencing (i.e. the {………../Motor…} syntax! Always use View custom props

1 Like