How complex can an expression tag practically be?

We have a lot of sensors being pulled in from modbus and reported over Sparkplug via (unfortunately intermittent) cellular connections. We want to monitor how many are getting actual modbus errors and haven’t just fallen off the cellular connection (as indicated by a quality of Bad_Stale).

This expression is the simplest check I’ve come up with to monitor if a tag (at tag_path) is probably in a bad modbus state:

((!isGood({tag_path}))&&(qualityOf({tag_path})!="Bad_Stale"))

I’ve made an expression tag using that and it returns True/False as expected for a single modbus register.

But what I really want is a counter that shows how many of a group of ~90ish modbus tags are in this state.

The most direct method I can see would be to wrap that expression in a if(x,1,0), duplicate/edit that for each modbus tag path to monitor, then wrap that in a sum().

Am I pushing Ignition too hard if I try to make a single expression tag with (2*90) tag references in it? Is there a saner way to get a tag with a count of non-Bad_Stale errors?

I would expect sum() to be perfectly happy coercing booleans to ints, I don't think you need an extra layer.

It's probably not ideal, but it's also probably not a very big deal. Basically, every tagpath given in curly braces in an expression will end up wiring up a 'tag change listener', and then whatever Ignition mechanism is in charge of that expression will ultimately end up getting 'this tag changed' notification(s) as appropriate. This makes it truly async, as opposed to a scripting based approach, which would have to poll the tags periodically, which might technically make for less load (at the expense of some memory to hold all the listeners).

Is there some way I could flip-flop the logic to only need to reference each tag once? Would that reduce the number of listeners? Like, I know I could do it with switch(qualityOf({tag_path}), ......) but there’s a LOT of cases in the list of qualities and I’d be worried about the expression breaking if quality codes change in the future.

Polling could be more manageable in this case, but I’ll need to find out how quickly this counter metric needs to be updated and if we’re okay with missing transients.

As a matter of interest, how does (or why would) isGood give a true if qualityOf is false?

My gut instinct is that expressions are 'smart' enough to coalesce the listeners created, but I'm actually not totally confident of that...

Either way, I don't think you can reduce the actual number of comparison needed for your exact case. And there's no short-circuiting in expression anyways, unfortunately...

I don’t know if this would be better… but you could use a gateway tag change script that specified all 90 tags, and then when that fired for any of the tags update a Dataset memory tag with the tag name in one column and the quality in another.

Then you could have just one or two expression tags that did calculations on the Dataset columns. Or even just calculate it as each tag change script fires and write to another memory tag.

1 Like

You can go even faster by just updating a jython dictionary in the top level of a project script. All of the tags will fire their initialChange to fully populate it on any code change. Any events with initialChange false can then compute the output.

Check my parenthesis more carefully. I'm watching for any condition that is not good except for "Bad_Stale".

I was hoping to have the list of modbus tags managed programmatically since it may have a few items added/removed every week or two. I know how to do that by reconfiguring the expression tag in scripting, but don't think I can do that with the gateway tag change script.

I've discovered an annoying oddity of using this async expression tag setup... If multiple of the modbus tags are coming from the same Sparkplug edge node, they will all flip between bad_stale and their true good/bad status very very rapidly but not atomically as the edge node goes offline/online.

Effectively, the expression tag re-evaluates itself multiple times rapidly, and some of the intermediary ones may run before all the source modbus tags have settled, giving the error count tag an in-between value. The final value it settles on is okay, but the in-between values may trip alarm events which will report the in-between values, even if I set timeOnDelaySeconds or timeOffDelaySeconds on the alarm.

In our case it's misleading but not horrible for a user to get an alarm saying "3 modbus slaves in error" when it really should've been "6 modbus slaves in error", but it's annoying.

I've been weighing the tradeoff of making the expression tag use a fixed time interval instead of being event driven to avoid these burst changes. I don't suppose there's a way to do "event driven expression with a required 2 second settle time"? :slight_smile:

There's nothing built in to the platform, no. I think it would require an extension to the expression tag mechanism...

It would also theoretically be possible to create a "delayed" expression function that just collects value changes and doesn't emit them within a time slice. Perhaps something for Ignition Extensions, or Phil might add it to his Simulation Aids module if he finds it useful :slight_smile:

That is remarkably challenging. Expression function implementations must return a value every time they are called.

Right, but the 'event driven' evaluation mode for tags allows the expression to 'drive' the value change backwards (by firing an event on the InteractionListener delivered during connect). I think you could use that, though, you're right, things would get weird if you set a tag group on the expression tag.

You'll note that my objectScript() function exposes this as binding, because that's what it has always been. I haven't played much with event-driven tags, but non-event-driven historically have delivered a null interaction listener. (So binding is None for those cases in objectScript.)

Having the option to retrigger later makes it possible to deliver extra, later, values, but all triggers have to return something. This is one of the reasons I am so horrified by almost any use of the tag() function.

In my specific case, it would be safe to return the prior value of the tag unchanged, but I imagine an expression function can't know that.

In a related note, I have sometimes wanted an expression tag that could latch and hold it's own prior value. Like the expression if({latch_enabled},{existing_tag_value},{new_tag_value}) ... but I imagine that's difficult for similar reasons.