Gateway scripts or expression

I just read a forum topic about expressions and got the question when should you use an expression and when a gateway script. I have for example an operator screen in my project that has to show certain information that contains tags and strings. I made it with the use of expression but find it a bit slow when tag values change.
The information that is displayed by the operator screen is made as follows:

if ({Root Container.Label.Prompt} = 2,"<html>Huidige dosering:"+" "+ {Root Container.Label.MateriaalNaam}+
"<BR>Setpunt is" +" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+ 
"<BR>Actual is"+" "+ {Root Container.Label.actual} + {Root Container.Label.engunit}+ 
"<BR>Tolerantie - / + :" +" "+ {Root Container.Label.tolMin}+"/"+{Root Container.Label.tolPlus}+{Root Container.Label.engunit},

if ({Root Container.Label.Prompt} = 3, "<html>Recept totaal is groter dan de maximale inhoud van de tank."+
"<BR>Maximale inhoud is"+" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+
"<BR>Recept grote is"+" "+ {Root Container.Label.actual} + {Root Container.Label.engunit},

if ({Root Container.Label.Prompt} = 4, "<html>Tank is momenteel vervuild."+
"<BR>Deze moet eerst gereinigd worden en dit moet bevestigd worden"+
"<BR>voordat we teruggaan naar standby.",

if ({Root Container.Label.Prompt} =5, "<html>Tappunt is momenteel vervuild."+
"<BR>Deze moet eerst gereinigd worden en dit moet bevestigd worden"+
"<BR>voordat we teruggaan naar standby.",

if ({Root Container.Label.Prompt} =6, "<html>Recept in tappunt bunker komt niet overeen"+
"<BR>met receptnummer in voordeegtank.",

if ({Root Container.Label.Prompt} =7, "<html>Temperatuur is niet gehaald binnen de ingestelde tijd!"+
"<BR>Setpunt is"+" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+ 
"<BR>Process waarde is"+" "+{Root Container.Label.actual}+ {Root Container.Label.engunit},

if ({Root Container.Label.Prompt} =8, "<html>Voeg schoonmaak componenten toe!"+
"<BR>Druk hierna op bevestigen",

if ({Root Container.Label.Prompt} =9, "<html>RunID:"+" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+  
" "+"BatchID:"+" "+{Root Container.Label.actual}+ {Root Container.Label.engunit}+  
" "+"Productnummer:"+" "+{[Test SCADA Ignition]Action/Action0/hmi/materialCode}+ {Root Container.Label.engunit}+
"<BR>Productnaam:"+
"<BR>Reden : ",

if ({Root Container.Label.Prompt} =10, "<html>RunID:"+" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+  
"<BR>Productnummer:"+" "+{[Test SCADA Ignition]Action/Action0/hmi/materialCode}+ {Root Container.Label.engunit}+
"<BR>Productnaam: ",
if ({Root Container.Label.Prompt} =11,	 "<html>Plaats een lege kuip onder het tappunt en bevestig dit erna.",
if ({Root Container.Label.Prompt} =12,	"<html>Batch is gereed verwijder kuip onder tappunt en"+
"<BR>plaats deze in een menger bevestig dit erna!",
if ({Root Container.Label.Prompt} =13,	"<html>Is het deksel van de aanmaaktank goed gesloten?"+
"<BR>Draai deze stevig vast en bevestig dit!",
if ({Root Container.Label.Prompt} =14,	"<html>Wil je de weegbunker in de koelcel meespoelen?"+
"<BR>Dit betekent dat het resterende voordeeg eruit wordt getapt!",
if ({Root Container.Label.Prompt} =15,	"<html>Voeg" +" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+" "+ "van<BR>"+
{Root Container.Label.MateriaalNaam}+" "+ "toe.",
if ({Root Container.Label.Prompt} =16,	 "<html>Plaats een lege bak op de weegschaal en bevestig dit",
if ({Root Container.Label.Prompt} =17,	"<html>Voeg"+" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+"van"+
"toe."+
"<BR>Gedoseerd :"+" "+{Root Container.Label.actual}+ {Root Container.Label.engunit},   
if ({Root Container.Label.Prompt} =18,	 "<html>Leeg de bak met componenten in de kuip.",
if ({Root Container.Label.Prompt} =19,	"<html>Leeg de bak met componenten in de aanmaaktank."+
"<BR>Bevestig dit hierna.",
if ({Root Container.Label.Prompt} =20,	"<html>Plaats lege kuip/bak onder de afvoer van de AMT."+
"<BR>Bevestig dit! Hierna zal de aanmaaktank beginnen met lossen!",
if ({Root Container.Label.Prompt} =21,	"<html>Gevraagde materiaalcode komt niet overeen met aanwezig product."+
"<BR>Voordeegbunker :"+" "+{Root Container.Label.actual}+ "/Gevraagd :"+" "+{Root Container.Label.setpoint}, 
if ({Root Container.Label.Prompt} =22,	"<html>Plaats bak onder stofafzuiging en laat het deksel zakken."+
"<BR>Deze melding verdwijnt automatisch.",
if ({Root Container.Label.Prompt} =23,	"<html>Stofafzuiging is niet ingeschakeld of heeft storing."+
"<BR>Klik op bevestigen om door te gaan zonder stofafzuiging.",
if ({Root Container.Label.Prompt} =24,	"<html>Controleer gist voorraad!"+
"<BR>Melder ziet geen product!"+
"<BR>Deze melding verdwijnt automatisch.",
if ({Root Container.Label.Prompt} =25,	 "<html>Geen voordeeg gereed voor productie!",
if ({Root Container.Label.Prompt} =26,	"<html>Sluit 1 zijde van de slang aan op het tappunt bij"+
"<BR>de voordeeg uitvoer en de andere kant op het riool."+
"<BR>Bevestig dit als het aangesloten is!",
if ({Root Container.Label.Prompt} =27,	"<html>Seal lekkage bij dispergeerpomp gedetecteerd!"+
"<BR>Controleer seal spoeling!",
if ({Root Container.Label.Prompt} =28,	"<html>laagniveau sensor laag."+
"<BR>Nogmaals wankelen of leegmelden?",
if ({Root Container.Label.Prompt} =29,	"<html>Lossen naar afvalbak is actief!"+
"<BR>Druk de drukknop bij de bak in om de klep te openen."+
"<BR>Let op! Zolang als deze melding hier actief is"+
"<BR>kan het riool niet gebruikt worden!",
if ({Root Container.Label.Prompt} =30,	"<html>Bevestig het schoonmaak koppelstuk"+
"<BR>om de menger te reinigen",
if ({Root Container.Label.Prompt} =59, "<html>Plaats een slang van gistaansluiting naar riool en bevestig dit.",
if ({Root Container.Label.Prompt} =60, "<html>Verwijder de slang tussen gistaansluiting en riool en bevestig dit."+
"<BR>Let op! Na bevestigen draait het carrousel automatisch.",
if ({Root Container.Label.Prompt} =200, "<html>Deeg temperatuur is te laag."+
"<BR>Gewenste temperatuur :"+{Root Container.Label.setpoint} +"°C"+
"<BR>Actuele temperatuur :"+ {Root Container.Label.actual} +"°C",
if ({Root Container.Label.Prompt} =207, "<html>Carrousel kan niet veilig draaien door"+ {Root Container.Label.L4_ID_Unit}+
"<BR>Controleer dat de tangen open staan.",
if ({Root Container.Label.Prompt} =205, "<html>Kuip in hefkieper bevat laatste batch van recept"+"<BR>Graag bevestigen dat hefkieper is schoongemaakt.",
if ({Root Container.Label.Prompt} =183, "<html>Het caroussel kan niet veilig draaien doordat de kop of klemming van kneder 1 niet veilig open staat. Controleer de positiesensoren van kneder 1.","")))))))))))))))))))))))))))))))))))

The prompt parameter here is different based on the tag value of 'operatorIndex'
I made it in such a way that the operatorindex is passed into a tag path of the prompt tag. the prompt tags are different in the name based on a value like so:

[Test SCADA Ignition]Action/Action1/hmi/prompt

this is one of the prompt tag tag paths. There are more of these tags but with a different number then 1. It also could point to:

[Test SCADA Ignition]Action/Action4/hmi/prompt

So I made it like this:
image

But now my question. Is it better to make this in a gateway script so that my project stays quicker or is it not that bad to leave it like this.
Like I said at the beginning, If operatorIndex changes it changes the prompt value and the text that it displays, but there is a delay for like a second or two.

Is it better to make gateway scripts overall instead of expressions? and what are great examples of when you use a gateway script and when an expression.

Vau... how many ifs are there... :crazy_face:

Why not use the case instead?

2 Likes

https://docs.inductiveautomation.com/display/DOC81/case

But before I try to change everything isn't it better to find a way in gateway script?
or is case already much more sufficient?

Short version, if you can use an expression you absolutely should.

Slightly longer version:

If the result is non-specific to the client and/or should be executed independent of running clients, then you can use a gateway script.

If the result is client specific, the operation must be executed in a client scope, in that case a properly structured (maybe even an improperly structured one) will always be more performant.

If the result does not need to be executed independent of running clients then a gateway script might not be the correct tool for the job.

In this case it appears that the result is client specific so it must be executed per client. So, I think you'll find that an expression is much more performant.

3 Likes

Okay, thanks for your answer!!

The `case()` version.
case {Root Container.Label.Prompt},
	2,"<html>Huidige dosering:"+" "+ {Root Container.Label.MateriaalNaam}+
		"<BR>Setpunt is" +" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+ 
		"<BR>Actual is"+" "+ {Root Container.Label.actual} + {Root Container.Label.engunit}+ 
		"<BR>Tolerantie - / + :" +" "+ {Root Container.Label.tolMin}+"/"+{Root Container.Label.tolPlus}+{Root Container.Label.engunit},
	3, "<html>Recept totaal is groter dan de maximale inhoud van de tank."+
		"<BR>Maximale inhoud is"+" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+
		"<BR>Recept grote is"+" "+ {Root Container.Label.actual} + {Root Container.Label.engunit},
	4, "<html>Tank is momenteel vervuild."+
		"<BR>Deze moet eerst gereinigd worden en dit moet bevestigd worden"+
		"<BR>voordat we teruggaan naar standby.",
	5, "<html>Tappunt is momenteel vervuild."+
		"<BR>Deze moet eerst gereinigd worden en dit moet bevestigd worden"+
		"<BR>voordat we teruggaan naar standby.",
	6, "<html>Recept in tappunt bunker komt niet overeen"+
		"<BR>met receptnummer in voordeegtank.",
	7, "<html>Temperatuur is niet gehaald binnen de ingestelde tijd!"+
		"<BR>Setpunt is"+" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+ 
		"<BR>Process waarde is"+" "+{Root Container.Label.actual}+ {Root Container.Label.engunit},
	8, "<html>Voeg schoonmaak componenten toe!"+
		"<BR>Druk hierna op bevestigen",
	9, "<html>RunID:"+" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+  
		" "+"BatchID:"+" "+{Root Container.Label.actual}+ {Root Container.Label.engunit}+  
		" "+"Productnummer:"+" "+{[Test SCADA Ignition]Action/Action0/hmi/materialCode}+ {Root Container.Label.engunit}+
		"<BR>Productnaam:"+
		"<BR>Reden : ",
	10, "<html>RunID:"+" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+  
		"<BR>Productnummer:"+" "+{[Test SCADA Ignition]Action/Action0/hmi/materialCode}+ {Root Container.Label.engunit}+
		"<BR>Productnaam: ",
	11,	 "<html>Plaats een lege kuip onder het tappunt en bevestig dit erna.",
	12,	"<html>Batch is gereed verwijder kuip onder tappunt en"+
		"<BR>plaats deze in een menger bevestig dit erna!",
	13,	"<html>Is het deksel van de aanmaaktank goed gesloten?"+
		"<BR>Draai deze stevig vast en bevestig dit!",
	14,	"<html>Wil je de weegbunker in de koelcel meespoelen?"+
		"<BR>Dit betekent dat het resterende voordeeg eruit wordt getapt!",
	15,	"<html>Voeg" +" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+" "+ "van<BR>"+
		{Root Container.Label.MateriaalNaam}+" "+ "toe.",
	16,	 "<html>Plaats een lege bak op de weegschaal en bevestig dit",
	17,	"<html>Voeg"+" "+{Root Container.Label.setpoint}+ {Root Container.Label.engunit}+"van"+
		"toe."+
		"<BR>Gedoseerd :"+" "+{Root Container.Label.actual}+ {Root Container.Label.engunit},   
	18,	 "<html>Leeg de bak met componenten in de kuip.",
	19,	"<html>Leeg de bak met componenten in de aanmaaktank."+
		"<BR>Bevestig dit hierna.",
	20,	"<html>Plaats lege kuip/bak onder de afvoer van de AMT."+
		"<BR>Bevestig dit! Hierna zal de aanmaaktank beginnen met lossen!",
	21,	"<html>Gevraagde materiaalcode komt niet overeen met aanwezig product."+
		"<BR>Voordeegbunker :"+" "+{Root Container.Label.actual}+ "/Gevraagd :"+" "+{Root Container.Label.setpoint}, 
	22,	"<html>Plaats bak onder stofafzuiging en laat het deksel zakken."+
		"<BR>Deze melding verdwijnt automatisch.",
	23,	"<html>Stofafzuiging is niet ingeschakeld of heeft storing."+
	"<BR>Klik op bevestigen om door te gaan zonder stofafzuiging.",
	24,	"<html>Controleer gist voorraad!"+
		"<BR>Melder ziet geen product!"+
		"<BR>Deze melding verdwijnt automatisch.",
	25,	 "<html>Geen voordeeg gereed voor productie!",
	26,	"<html>Sluit 1 zijde van de slang aan op het tappunt bij"+
		"<BR>de voordeeg uitvoer en de andere kant op het riool."+
		"<BR>Bevestig dit als het aangesloten is!",
	27,	"<html>Seal lekkage bij dispergeerpomp gedetecteerd!"+
		"<BR>Controleer seal spoeling!",
	28,	"<html>laagniveau sensor laag."+
		"<BR>Nogmaals wankelen of leegmelden?",
	29,	"<html>Lossen naar afvalbak is actief!"+
		"<BR>Druk de drukknop bij de bak in om de klep te openen."+
		"<BR>Let op! Zolang als deze melding hier actief is"+
		"<BR>kan het riool niet gebruikt worden!",
	30,	"<html>Bevestig het schoonmaak koppelstuk"+
		"<BR>om de menger te reinigen",
	59, "<html>Plaats een slang van gistaansluiting naar riool en bevestig dit.",
	60, "<html>Verwijder de slang tussen gistaansluiting en riool en bevestig dit."+
		"<BR>Let op! Na bevestigen draait het carrousel automatisch.",
	200, "<html>Deeg temperatuur is te laag."+
		"<BR>Gewenste temperatuur :"+{Root Container.Label.setpoint} +"°C"+
		"<BR>Actuele temperatuur :"+ {Root Container.Label.actual} +"°C",
	207, "<html>Carrousel kan niet veilig draaien door"+ {Root Container.Label.L4_ID_Unit}+
		"<BR>Controleer dat de tangen open staan.",
	205, "<html>Kuip in hefkieper bevat laatste batch van recept"+"<BR>Graag bevestigen dat hefkieper is schoongemaakt.",
	183, "<html>Het caroussel kan niet veilig draaien doordat de kop of klemming van kneder 1 niet veilig open staat. Controleer de positiesensoren van kneder 1."
)

The speed advantage will be checking {Root Container.Label.Prompt} just once and jumping straight to the relevant line. Your nested if check over and over until it finds a match - or doesn't!

Oh - and it is readable!

3 Likes

Pssst! Use lookup() with a constant dataset of value-string combinations tucked away in a memory tag.

8 Likes

you're saying that instead of the strings typed in the expression and added the tag values after them, put them into a dataset? But how can I set tag paths or parameters in the a dataset with strings in it?

You can't with your dynamic HTML.

and there is no work around? or maybe just without html

Ah, that's a little more tricky.
Provide the dataset with strings that have printf-style placeholders with numbered arguments. Wrap the lookup() inside of stringFormat(), and provide all possible arguments to stringFormat(). It would look something like this:

stringFormat(
	lookup(
		{Root Container.Label.strings},
		{Root Container.Label.Prompt},
		"<html>Unknown Prompt Code!",
		'Prompt',
		'Pattern'
	),
	{Root Container.Label.setpoint},
	{Root Container.Label.actual},
	{Root Container.Label.engunit}
)

The dataset I tested had this content:

"#NAMES"
"Prompt","Pattern"
"#TYPES"
"I","str"
"#ROWS","2"
"7","<html>Temperatuur is niet gehaald binnen de ingestelde tijd!<BR>Setpunt is %1$.1f%3$s<BR>Process waarde is %2$.1f%3$s"
"8","<html>Dummy"

Prompt #7 looked like this:
stringFormat-Numbered-Placeholders

The numbered placeholders work because Ignition's stringFormat() is a thin wrapper around Java's String.format() method, which delegates the hard work to java's Formatter class.

If you need any other arguments, simply include them, and reference them by number. Unused arguments will be ignored. Since you reference them by number, you do not need to use them in the string in the order provided, and can use them multiple times.

(For testing, I just put the dataset in a custom property. In production, I'd use a Vision Client Tag, and I'd arrange for the strings to be swapped in that client tag based on the client's chosen runtime language.)

5 Likes

Okay thanks, I will try your suggestion and see if I can make it work.

nevermind I found what the problem was

Thanks Pturmel!! It works!! This is exactly like I want it to be.

There is one thing though, maybe someone knows a solution to it.


The picture above is my operator screen and the buttons on the side make it possible to see the other alarm in the operator screen. I made this with the following script for the buttons:

def find_activeAlarm_tags():
        # Initialize an empty list to store matching tag paths
        Alarm_tags = []
    
        # Define the range of tag names
        for i in range(24):
            action_tag = "Action" + str(i)
    
            # Create a tag path for the current tag
            tag_path = "[Test SCADA Ignition]Action/{}/hmi/prompt".format(action_tag)
            tag_Nrpath = "[Test SCADA Ignition]Action/{}/obj/nr".format(action_tag)
    
            # Get the tag value
            tag_values = system.tag.readBlocking([tag_path])
            tag_Nr =  system.tag.readBlocking([tag_Nrpath])
    
            # Check if the prompt value is greater than 0
            if tag_values[0].value > 0:
                # Add the tag path to the list if the condition is met
                Alarm_tags.append(tag_Nr)
    
        # Return the list of matching tag paths
        
        return Alarm_tags

This script simply searches through my Prompt tags and filters the ones that are higher then 0 ( The alarms that are on). But when I press the button it has a delay for like 2 seconds before my other alarm information is being displayed on my operator screen.

If it is not clear what I mean I can try to explane better but If it is clear, maybe someone know a solution to make it react quicker?

You have 48 readBlocking events in that script.

This has two.

def find_activeAlarm_tags():
    # Initialize an empty list to store matching tag paths
    tagPaths = []
    tagNrPaths = []
    Alarm_tags = []

    # Define the range of tag names
    for i in range(24):
        action_tag = "Action" + str(i)
        # Create a tag path for the current tag
        tagPaths.append("[Test SCADA Ignition]Action/{}/hmi/prompt".format(action_tag))
        tagNrPaths.append("[Test SCADA Ignition]Action/{}/obj/nr".format(action_tag)
    
    # Get the tag values
    tag_values = system.tag.readBlocking(tagPaths)
    tag_Nrs =  system.tag.readBlocking(tagNrPaths)

    for i in range(24):
        # Check if the prompt value is greater than 0
        if tag_values[i].value:
            # Add the tag path to the list if the condition is met
            Alarm_tags.append(i)

    # Return the list of matching tag paths
    return Alarm_tags

See if that makes a difference.
Then see if you can reduce it to one readBlocking.


Tip: you have three different font sizes and two different styles (normal and bold) on that screenshot as well as buttons breaking into the text area. It is not nice and visually very distracting! Have a look at some visual style guides to get some ideas for better layout.

1 Like

It give me a error saying that the following

Traceback (most recent call last):
  File "<event:actionPerformed>", line 1, in <module>
  File "<module:find_activeAlarm_tags>", line 16, in find_activeAlarm_tags
TypeError: expected a str

I believe this should be system.tag.readBlocking(tagNrPaths).

Single read version:

from itertools import chain

tags_paths = list(chain.from_iterable(
	(
		"[Test SCADA Ignition]Action/Action{}/hmi/prompt".format(n),
		"[Test SCADA Ignition]Action/Action{}/obj/nr".format(n)
	) for n in xrange(24)
))

qvals = system.tag.readBlocking(tags_paths)
tags_values = [qval for qval in qvals[::2]]
tags_nrs = [qval for qval in qvals[1::2]]

return [nr for val, nr in zip(tags_values, tags_nrs) if val > 0]

Could be made more unreadable with some chunking if you're interested in making ennemies.

5 Likes

Correct, and fixed. Thanks.

@Stef_Velers, my original script failed because I had created a list of tags, ["tag1", "tag2", ...] and then enclosed that in [ ] which made it into [["tag1", "tag2", ...]] (which won't work).

correct I saw that.
But the script returns nothing sad enough. Not sure why cause it looks right to me. But I'm not a coding expert :slight_smile: