Expression Binding

I’m using Ignitions built in expression binding tool to determine what color a box should be based on an if statement. The code I currently have is as follow but I cannot get it to work properly.

IF({[default]CP01/VISICON/VISICON_D_1_CONV_Running},6,IF({[default]CP01/VISICON/VISICON_D_1_CONV_Drive_Connection_Fault}|| {[default]CP01/VISICON/VISICON_CAPELLA_E_Stopped} || {[default]CP01/VISICON/VISICON_E_Stop_Fault},1,IF({[default]CP01/VISICON/VISICON_D_1_CONV_VFD_MTR_Fault},1,IF({[default]CP01/VISICON/VISICON_D_1_CONV_Flow_Stop},8,IF(!{[default]CP01/VISICON/VISICON_D_1_CONV_No_Jam_Faults},2,IF({[default]CP01/VISICON/VISICON_D_1_CONV_Running_but_Not_Commanded_to_Run},1,8))))))

That’s a very convoluted truth table to follow, especially since many branches end up at the same result.
I would, at the absolute minimum, suggest heavy use of whitespace to try to make things easier to follow:

IF(
	{[default]CP01/VISICON/VISICON_D_1_CONV_Running}
	,6
	,IF(
		{[default]CP01/VISICON/VISICON_D_1_CONV_Drive_Connection_Fault}
			|| {[default]CP01/VISICON/VISICON_CAPELLA_E_Stopped}
			|| {[default]CP01/VISICON/VISICON_E_Stop_Fault}
		,1
		,IF(
			{[default]CP01/VISICON/VISICON_D_1_CONV_VFD_MTR_Fault}
			,1
			,IF(
				{[default]CP01/VISICON/VISICON_D_1_CONV_Flow_Stop}
				,8
				,IF(
					!{[default]CP01/VISICON/VISICON_D_1_CONV_No_Jam_Faults}
					,2
					,IF(
						{[default]CP01/VISICON/VISICON_D_1_CONV_Running_but_Not_Commanded_to_Run}
						,1
						,8
					)
				)
			)
		)
	)
)

But I do wonder if you wouldn’t be better off using a series of inputs to binEnc; I personally think that logic would be easier to follow.

2 Likes

So using the new expression statement you provided, at the bottom I still get an error from the expression (it got a null first argument). I’m not really sure what is wrong, this expression is being linked to a fill paint color that we want to automatically change depending on which tag is being pointed to. I have tried an elif statement but the else portion isn’t supported in the expression.

TBH, I think binenc would make it harder to follow. It would make the most sense to me to combine the conditions that result in the same value, but perhaps some conditions have more precedence than others? . For multiple nested ifs, these for me are essentially case statements, and I treat them as such by putting them on the same indent as I find it reads more easily (this is going to be fun on a phone…) :

IF({[default]CP01/VISICON/VISICON_D_1_CONV_Running}
	,6
,IF({[default]CP01/VISICON/VISICON_D_1_CONV_Drive_Connection_Fault}
	|| {[default]CP01/VISICON/VISICON_CAPELLA_E_Stopped}
	|| {[default]CP01/VISICON/VISICON_E_Stop_Fault}
	,1
,IF({[default]CP01/VISICON/VISICON_D_1_CONV_VFD_MTR_Fault}
	,1
,IF({[default]CP01/VISICON/VISICON_D_1_CONV_Flow_Stop}
	,8
,IF(!{[default]CP01/VISICON/VISICON_D_1_CONV_No_Jam_Faults}
	,2
,IF({[default]CP01/VISICON/VISICON_D_1_CONV_Running_but_Not_Commanded_to_Run}
	,1
	,8
)))))) 
1 Like

Yeah, without knowing the actual logic at play here it's hard to say.
In a more normal programming context, I'd say to break things up into intermediate booleans; you could do that here via additional custom properties?
So you could define an additional property (make it private for the sake of performance) and have it essentially be:

generalFault = {[default]CP01/VISICON/VISICON_D_1_CONV_Drive_Connection_Fault} 
	|| {[default]CP01/VISICON/VISICON_CAPELLA_E_Stopped}
	|| {[default]CP01/VISICON/VISICON_E_Stop_Fault}
	|| {[default]CP01/VISICON/VISICON_D_1_CONV_VFD_MTR_Fault}

And then refer to {generalFault} in your IF; you could also do the same thing to break nested IFs into individual pieces. That also improves debugging; you can see which "piece" of logic has an error at a glance.

2 Likes

Alright so we have decided to try a slightly different approach to this issue. This time I am working in the samplequicksatart Ignition Project due to issues with things not yet existing to work in my actual project environment. As you can see I have an icon here and the color is linked to WriteableInteger2, the binding also has a list of colors that it can be changed to based on what number is set, and it successfully works like this.


However, I need to be able to bind several tags within this expression space inside IF statements. If I add the second IF statement to in the expression binding, it no longer works.

if({[Sample_Tags]Writeable/WriteableInteger2} = 2,2,0)
if({[Sample_Tags]Writeable/WriteableInteger3} = 6,6,1)

Adding a coma between them also doesn’t work but adding the || (logical OR) operator returns a TRUE statement but does not set the color like I want.


In the actual project the objective is for the icons color to change based on several different tag values. Depending on which tag is actively giving a value (such as 1) the icon should show red, as soon as that value changes to 0 it needs to go back to the default state (or fallback). But there are different tags all pointing to the same icon that would control its color depending on their value.

I wouldn't expect that to work.

  1. An expression has to resolve to a single returned value
  2. A logical OR of two integer values will return either True or False (1 or 0)

Trying to do this in an expression the way you are is going to be difficult to follow and probably have precedence issues.

For instance if you wrote your expression as follows:

if({[Sample_Tags]Writeable/WriteableInteger2} = 2, 2, 
    if({[Sample_Tags]Writeable/WriteableInteger3} = 6,6,1))

You should see that if WriteableInteger2 = 2 then you get 2 no mater the value of WriteableInteger3. WriteableInteger2 takes precedence over WriteableInteger3.

Perhaps that is the behavior you want, but maybe it is an unintended side effect.

Many to One relationships are difficult to display and precedence must be considered.

Pesudo expression as an example:

if({Highest Precedence Tag},value, if({lowest Precedence Tag}, value, fallback))

You can keep nesting if() from highest to lowest precedence but the expression will get more difficult to follow the more tags you introduce.

1 Like

One thing I would also do is remove your magic colours and replace them with style classes. Magic colours are horrible to use and maintain :weary: unless of course these are just for your testing?

3 Likes

They are just for testing.

Figured I would go ahead and post this as I believe I have found a solution.
So rather than using WriteableIntegers I used WriteableBooleans like so:


Where the “= x,y,z” corresponds to:
x = boolean true or false (1 or 0)
y = the map color for if the x is true
z = the map color for if the x is false.
I also changed all the || (logical OR) into just | (bitwise OR) as I wanted a numerical value for transform map rather than a boolean true/false.
The objective was to change the color of an icon depending on which tags were true. The top-most IF statement takes precedence over the bottom-most so if Boolean2 and Boolean7 are true, the icon would appear as Boolean2’s color mapping, once Boolean2 clears to false, Boolean7’s color map becomes the icon color.
I haven’t gotten to try this in my actual project (which is where it needs to work) so I will update on if it works there too when I get to try it out.

A few things, just for information more than anything.

  1. There is no need for the equivalency check
  2. There is no need for the bitwise OR

This expression will return the exact same results:

if({[Sample_Tags]Writeable/WriteableBoolean1},6,
if({[Sample_Tags]Writeable/WriteableBoolean2},1, 
if({[Sample_Tags]Writeable/WriteableBoolean3},1,
if({[Sample_Tags]Writeable/WriteableBoolean4},1,
if({[Sample_Tags]Writeable/WriteableBoolean5},1,
if({[Sample_Tags]Writeable/WriteableBoolean6},8, 
if({[Sample_Tags]Writeable/WriteableBoolean7},2,
if({[Sample_Tags]Writeable/WriteableBoolean1},1, 8))))))))

Anything bitwise OR’d with 0 will just return itself, so you can remove all the 0 | whatever. The boolean tag’s will resolve to True or False no need to force it with the equality.

3 Likes

You could create a custom property on the control and do a concat expression of toInt for each tag then do your color table on the custom property value.

concat(toInt({[Sample_Tags]Writeable/WriteableBoolean1}),
toInt({[Sample_Tags]Writeable/WriteableBoolean2}),
toInt({[Sample_Tags]Writeable/WriteableBoolean3}),
toInt({[Sample_Tags]Writeable/WriteableBoolean4}),
toInt({[Sample_Tags]Writeable/WriteableBoolean5}),
toInt({[Sample_Tags]Writeable/WriteableBoolean6}),
toInt({[Sample_Tags]Writeable/WriteableBoolean7}))

With how you have it set up now, wouldn’t a case() with a binEnum() be cleaner and easier to maintain if you have to add to it later?

case(binEnum(tag1,tag2,tag3,tag4....)
	,1,6
	,2,1
	,3,1
	,4,1
	,5,1
	,6,8
	,7,2
	,8,1
	,8)

Late to the party but one way I did this before was to have a color mapping dictionary script module. So you could have something like this -

# Use whatever color convention your project uses
table_colors = {
    "some_condition":"color(123,111,222,255)",
    "other_condition":"red",
    ...
}

def getColor(condition,defaultColor):
    try:
        return table_colors[condition]
    except KeyError:
        return defaultColor

and use this in a runScript expression, feeding in the current condition of the window and some default color in case.

You might have multiple different color dictionary definitions per window, in which case you would need multiple data dictionaries, and an additional parameter to the function indicating what dictionary to use. But it worked out nicely for me.

I normally try to avoid run script due to performative issues and gateway round trip stuff, but using it to just try/except getting a key from a dictionary as ways of making a “switch” statement, and then giving a default case via except KeyError, works pretty fast for me and also simplifies expressions enough that it’s worth it for me.