Advice on how to build a "timer" for an assembly station

I have a pretty generic question that I couldn't really find an answer for on the forums, but I do think it can be applicable to other peoples projects as well. If this question is already answered elsewhere, link it here and I'll remove/edit this question.

The basic idea of what I'm trying to do is display a timer countdown component (using the Perspective Simple Gauge component) for each station in an assembly line. Assume in this case there will be at most 50 stations. What I mean by timer is similar to a physical timer - I set a 20 minute timer on my phone to take a nap, and I can be sure the timer will go off in 20 minutes (whether I wake up is a different story).

This component in my project is similar - given a Takt time for a station and a start time, I would like to use the gauge to display a decreasing gauge value every second, like a handheld timer.

Scenario:

Station A has a Takt time of 300 seconds (5 minutes). An assembly arrives at Station A (how an arrival is determined is irrelevant for now), and the second it arrives at the station, we capture a start time. Then, our Gauge component would start from 300 and decrease every second by one value (Takt is measured in seconds as well).
image

image

image

Not exactly rocket science... What I want advice on is how to best accomplish this using the timers that are available in Ignition in a way that's scalable and not resource-intensive.



**A high level approach I've thought of: use a gateway timer script and write to a "heartbeat" tag every second, starting from 0 at midnight up to 86399. Then, when an assembly arrives, capture the start time using system.date.now() and the heartbeat tag value. I would bind the gauge's value property to an expression similar to

TaktTime - (CurrentHeartbeat - StartHeartbeat)

where CurrentHeartbeat will come from a tag and will be what triggers the gauge's value property to refresh. Too complicated? Too simple? I don't know...

I appreciate any and all ideas are appreciated. There are many ways to accomplish this, as is common in Ignition, but I'm looking for the most bomb-proof, least resource-intensive method possible.

Not really a direct answer to your question, just curious why you’re not doing this at the machine level? Seems like something that is much better suited for the PLC. Assuming that your clocks are all synchronized, would be much more accurate.

1 Like

Do you mean capturing cycle times at the machine level or using a PLC tag for a heartbeat?

There are a few things I can try to explain - this is for a customer, so we don't have access to their controllers. It's pretty easy to record cycle times (and, in fact, that's what we're doing using transaction groups, RFID readers, and limit switches) and its also trivial to set up a heartbeat in a PLC tag. To be honest, though, I work more on the MES side of Ignition, so what I just said might be completely wrong. From my current understanding, that's how it works.

That still wouldn't help me as I'm designing the UI - whether the "heartbeat" is coming from a PLC or a gateway timer script (which seems to be working fine), this customer really wants to see the gauge updating every second.

The question sort of came up as a way for me to get the above-described behavior but to also avoid using a now(1000) statement in an expression somewhere, where it executes on every client. I believe that with the method I described above, I will have the behavior I want AND also display the gauge at the same state for all clients that have the Station view open for that specific station.

I look forward to you critiquing my approach - you always have thoughtful answers on the forum.

Consider making a UDT housing a timestamp memory tag, a takt time parameter (or perhaps a memory tag), and a few expression tags. The key is an expression tag that computes remaining time:

max(0.0, {[.]TaktSeconds} - dateDiff({[.]StartTS}, now(0), 'second'))

Arrange for your start event to write the current timestamp to the StartTS tag. Point your gauge at the remaining time tag, and scale it to TaktSeconds. You could make an alarm when it reaches zero. Or adjust the max() expression to allow some amount negative and alarm at a certain amount negative.

Assign the remaining time expression tag to a group that executes at a pace suitable for your responsiveness needs.

1 Like

This is describing pretty much exactly what I have at the moment, except that instead of a direct tag group driving the update, I have the gateway "heartbeat" like I described above.

image

I'm going to add the RemainingTime tag as you described, just to see the differences.

A few questions/comments:

  1. I think the max() expression probably isn't necessary in this case (although a good idea generally) - they want to know what the actual cycle time even if its grossly greater than the takt time.

  2. Noob question: what do you mean "scale it to TaktSeconds"? Do you mean setting maxValue? I created a direct tag group that executes every second, but when I write to OperationStartTime, my remaining tag just stays at the same value as TaktTime... It isn't decreasing as I would expect. Here is the RemainingTime expression tag:

  3. Since the gauge is going to have a binding and there will be times when the station is not "active" i.e. no assembly at the station (for potentially long periods of time like a weekend), would the paradigm you described above allow the binding to only refresh when there in an assembly in the station (when OperationStartTime isn't null)? The way I was going to accomplish with the method I described above was to have an expression tag called IsActive (which you can see in the snip above) and it basically is 1 when the OperationStartTime tag isn't null. Then, in my binding, I would have something like

IsActive * (TaktTime - (HeartbeatCurrent - HeartbeatStart))

that way, when there isn't an assembly at the station, the gauge's value property would be 0, the value would be 0, and the binding wouldn't refresh until IsActive is 1 again.

I don't know if any of that made sense... Thank you for the feedback so far!

The reason why my tag wasn't decreasing was because the RemainingTag execution mode was set to Event Driven instead of Tag Group, so it is now working properly!

As always, @pturmel once again comes with a great solution, where the binding doesn't refresh more than it needs to AND it eliminates the buggyness of my proposed approach (although it would've been workable with a few extra checks).

The potential bug I'm referring to specifically is the case when assembly starts at a station when the Heartbeat tag value is close to the mod value. In other words, say my mod value for the gateway timer tag was 3600 (every hour resets back to 0) - what happens when I start an order when the heartbeat is at 3500 and the order runs for 300 seconds?

Based on the expression I threw out in the OP,

TaktTime - (CurrentHeartbeat - StartHeartbeat)

would result in

300 - (200 - 3500) = -3400 

which is obviously wrong and would result in a drastic jump in the gauge component value. This wouldn't affect the actual duration since those are captured by timestamp, but would result in a jarring experience for the operators at seemingly random times.

I tested Phil's suggestion and am satisfied that it meets the requirements I laid out. I even made sure to run it past the 0 mark, and it still did exactly what I wanted it to do.

Thank you again for your responses!

Yeah, not a fan of the heartbeat tag.

The scaling point is simply for the max value of the gauge. Remaining time == takt time at the start, when the gauge should show max.

The IsActive multiplication would work for my original math, too. Heartbeat not needed.

1 Like