Ignition Designer Freezing with a History Tag

Hey guys,

I have had a crack at trying to create run time displays to track how long machinery and devices such as pumps etc have been operating for to help the maintenance team. I am looking for some feedback to fix some issues I am having with the designer freezing when my template is on a screen. I am pretty new with ignition, so bear with me if my method for achieving this is sinful.

I will start with the issue, because maybe it is an easy fix but I have details of what I have done below

The Issue:

When the template I have created with a numeric display is on a screen with no tag bindings it is fine, once I link a tag to a template property the designer freezes (runs awfully slow) as noted in previous forum posts. I have checked for circular references and couldn’t find any so hoping some of you wizards could help me out.

This method was working for a few weeks then now it doesnt and I am unsure what I have changed to cause this. I know that it is this particular template because I deleted screen components in the XML one by one until I found this template to be the issue.

Background:

So I have turned on tag history on some Boolean tags that indicate when a device is on, example below.

I have then created a project script that given the inputs of the desired tag path, will return how many hours the tag has been active and outputs a float.

def getRuntimeHours(tagPath, startDate=None, endDate=None):
    """
    Returns total runtime (hours) for a boolean historized tag.
    """

    import system
    from java.util import Date

    try:
        # ---- Sanitize tag path ----
        if tagPath is None:
            return 0.0

        tagPath = str(tagPath).strip()

        if tagPath == "":
            return 0.0

        # ---- Time defaults ----
        if endDate is None:
            endDate = system.date.now()

        if startDate is None:
            startDate = system.date.getDate(2026, 0, 1)

        # ---- Query history ----
        result = system.tag.queryTagHistory(
            paths=[tagPath],
            startDate=startDate,
            endDate=endDate,
            aggregationMode="DurationOn",
            returnSize=1
        )

        if result is None or result.rowCount == 0:
            return 0.0

        # DurationOn returns SECONDS
        secondsOn = result.getValueAt(0, 1)

        if secondsOn is None:
            return 0.0

        return float(secondsOn) / 3600.0

    except Exception as e:
        # Throttled logging (won’t spam every second)
        system.util.getLogger("RuntimeCalc").warn(
            "Runtime calculation failed for tagPath=%r : %s" % (tagPath, e)
        )
        return 0.0

I have then made a template with a numeric display and a label with custom properties, show below.

Finally, I have the following expression script binded to the value of the numeric display

runScript(
"maintenance.getRuntimeHours",
1000,
toString({Run Time Display.tagPath})
)

I tried to give you guys as much detail so hopefully one of you wizards can point out what I am doing wrong.

Cheers

Multiple things going wrong here:

  • Never import libraries within a function. Imports should be at the top of a script library. Performance impact
  • Never import system. This is always* available and can cause issues if imported.
  • You’re trying to run a history query from the start of this year until now, every second. This is impossible. You’re likely ending up with stacked threads all querying tag history, and they’re building up not able to keep up with your request rate. The database is also being hammered with queries looking at significant amounts of data.

I would be curious to see exactly how long it does take the script to run..

However, I'll say that I would move this into a tag instead which accumulates hours and historise the tag if you need to trend it, but use the accumulator tag value to show runtime, not queries / tag history. Ideally the tag should be accumulated in the plc, not scada. But if youre just wanting “decent” runtime values, scada will be OK.

I think this looks OK, although I don't know how accurate it will be…

A bit dated though.

For the expression tag expression I'd be using:

if({[.]running tag}, getSecond(now()) % 2, -1)

That will flip flop the value every second to create the accumulation pulse trigger. Use that to trigger its value change script to run if value >=0 which reads the current value of your memory accumulator tag, and add 1s to it

Note: it's not usually recommended to call tag.read / writeBlocking from tag event handlers since they can sometimes take time to read if there are issues, but memory tags are an exception.

3 Likes

What’s the * explanation for; always but not always?

And in what era did this become bad practice?

I have scripts from 7.8 era that have made their way through 7.9 and now 8.1 systems (vision only) that in my early days I got code snippets from this forum, they have import system, thus “my” code has them, and I’ve never experienced a bad side effect as a result.

In some contexts, such as when writing a script directly in the script window in a Gateway Event, the system namespace is not available due to legacy scoping. This is one of the reasons that it is recomended that any gateway events should be one liners which delegate tasks to a library script where the LegacyScoping is not an issue. This is true in 8.1, I do not know about 8.3.

1 Like

This really needs put into the/a PLC if you can. This way you can have an accurate runtime calculation or 2 with reset capabilities. It will be more accurate, and if you have a couple of them, you can have a lifetime runtime and a maintenance runtime that you can then use for alarms if you want to remind the maintenance team to check it out periodically.