Resettable runtime counter

Hello,
I'm trying to figure out how to create an expression for a runtime counter.
I found this script that works:

if({[~]P_1_RI.value},
if(IsGood({[~]P1_RT.value}),{[~]P1_RT.value},0)+1,
if(IsGood({[~]P1_RT.value}),{[~]P1_RT.value},0))

However, I'm trying to figure out how I can add a 'reset' function that will allow the operator to reset the runtime counter to 0 if the button is pushed.

Additionally, sometimes the operator will need to manually update the runtime counter and then the system will continue to count up from that number.
Can anyone point me in the right direction?

I'm having a hard time figuring out what exactly this counter does...

Anyway, you can write to tags with the system.tag.write* functions:

def reset_counter():
    system.tag.writeBlocking(["[~]P1_RT"], [0])
1 Like

This particular request was a runtime counter for a motor. The client is used to being able to reset the counter at will. It scans at a fixed 1,000 ms rate.

This is the way I did it:

if ({[~]P1_RT_Reset.value}, 0,
    if ({[~]P1_RI.value},
        {[~]P1_RT.value} + 1,
        {[~]P1_RT.value}))

It'll just increment the runtime (RT) counter (displayed in hours) every 1 second that the run indicator (RI) is turned on.

Well, no. Not accurately. Tag execution intervals are approximate, and heavy load on the gateway will add to the inaccuracy.

Plus, your expression's use of itself makes a circular reference that will almost certainly choke on gateway restart.

The reliable way to have such a timer is to use a single datetime memory tag that holds the timestamp of the most recent reset.

Wherever you need to display the seconds since that timestamp, use:

dateDiff({[provider]path/to/memory/tag}, now(), 'second')

Whatever script you are using to write to your reset tag, change to simply write the current timestamp (from system.date.now()) to the memory tag.

Precise, zero gateway workload, simple reset. Even "counts" during gateway restarts.

Edit: Hmmm. The above doesn't handle the run indicator. A more complicated solution is needed for accuracy and robustness for such:

  • A memory tag of type double to hold "Prior Runtime Seconds"

  • Two memory tags of type datetime to hold the last "On" and "Off" timestamps of the run indicator.

  • A valueChange event script on the run indicator tag to intelligently update those memory tags (presumably all in the same tag folder). Something like this:

def valueChange(....):
	prior, onTS, offTS = [x.value for x in readBlocking(['[.]PriorSeconds', '[.]OnTimestamp', '[.]OffTimestamp'])]
	if currentValue.value:
		# Run Indicator is ON
		if offTS.after(onTS):
			# Really is transition to ON
			system.tag.writeBlocking(['[.]OnTimestamp'], [system.date.now()])
	else:
		# Run Indicator is OFF
		if not offTS.after(onTS):
			# Really is transition to OFF
			now = system.date.now()
			interval = 0.001 * (now.time - onTS.time)
			system.tag.writeBlocking([['[.]PriorSeconds', '[.]OffTimestamp'], [prior + interval, now])
  • Your reset operation simultaneously writes now() to the OnTimestamp and 0.0 to the PriorSeconds tags.

  • Your display expression becomes:

if(
	{[provider]path/to/run/indicator},
	{[provider]path/to/PriorSeconds} + dateDiff({[provider]path/to/OnTimestamp}, now(), 'second'),
	{[provider]path/to/PriorSeconds}
)
4 Likes

Hello,

I'm now getting errors that readBlocking, , .after, and .time are not valid attributes. I put this script into the tag editor > value changed script editor. Is this correct?

"readBlocking" should be system.tag.readBlocking in that sample.

def valueChange(....):
	prior, onTS, offTS = [x.value for x in system.tag.readBlocking(['[.]PriorSeconds', '[.]OnTimestamp', '[.]OffTimestamp'])]
	if currentValue.value:
		# Run Indicator is ON
		if offTS.after(onTS):
			# Really is transition to ON
			system.tag.writeBlocking(['[.]OnTimestamp'], [system.date.now()])
	else:
		# Run Indicator is OFF
		if not offTS.after(onTS):
			# Really is transition to OFF
			now = system.date.now()
			interval = 0.001 * (now.time - onTS.time)
			system.tag.writeBlocking([['[.]PriorSeconds', '[.]OffTimestamp'], [prior + interval, now]])

I am now getting no error in the tag diagnostics, however, the OnTimestamp and OffTimestamp are not being written to at all.

Did you create those memory tags with the right data types, and set a non-null initial value?

OnTimestamp = 2024-09-13 2:05:00 PM
OffTimestamp = 2024-09-13 2:00:00 PM
PriorSeconds = 5

These were manually set to test

Add some logging after the readBlocking and see if you are getting expected values in the gateway log.

Can you advise how to add the logging after the read blocking?

logger = system.util.getLogger('someName')
value = 2
logMsg= 'Value: {}'.format(value)
logger.infof(logMsg)

Something like this:

	logger = system.util.getLogger("Austin")
	logger.infof("prior=%s, onTS=%s, offTS=%s", prior, onTS, offTS)

In the gateway log I received:

prior=5.0, onTS=Fri Sep 13 14:05:00 CDT 2024, offTS=Fri Sep 13 14:00:00 CDT 2024

That's it? Nothing more when you toggle the boolean?

See screenshot for what is coming in whenever bool is toggled on and off

Hmmm. Alter the logging like so:

	logger = system.util.getLogger("Austin")
	logger.infof("current=%s, prior=%s, onTS=%s, offTS=%s", currentValue, prior, onTS, offTS)

The error message only occurred on the first toggle, does not happen again

Script:

Make sure your Prior tag is an integer long integer, int8, not a string or float a float (4 or 8).

Its currently set to a double, i changed it to float and still not working.