OPC Tag Value Seconds to HH:MM:SS via Expression Tag

I’ve read through a few examples of converting seconds to HH:MM:SS but none of them seem to directly address or fully solve my problem. Apologies if I missed a better question and resolution elsewhere.

I have a series of modbus registers that return a runtime in seconds as a float value. I would like to use an expression tag to provide a string (not date time) representation of the seconds in HH:MM:SS format, think stopwatch and not clock.

I have found the following script to (mostly) work:

if(isNull({Tag.Value}),"0:00:00",stringFormat("%f:%02f:%o2f",floor({Tag.Value}/3600),floor({Tag.Value}%3600/60),floor({Tag.Value}%60)))

I successfully get the right HH:MM:SS string, but the each integer in the string have a decimal and six 0’s trailing.

I have attempted to use round w/ 0 on each tag value before floor and after floor. I have attempted converting to string first and then changing the stringFormat appropriately.

I’m certain it is simple and I’m missing the obvious. Thanks in advance for your advice (or redirect to the topic I missed elsewhere).

Have you tried using %02d as the string format specifier instead?

I have, but I will go try that again. I should have mentioned, in case in matters, that I am viewing this via the Status → Tags portion of the web UI as well as the designer.

Here is what I see in the web UI:

tag-status-minutes

I am changing the script now for the %d/%02d format now.

I get an error that the value isn’t a double.

[null, Error_ExpressionEval(“d != java.lang.Double”)

Try this:

if(
	isNull({[~]Tag}),
	"0:00:00",
	stringFormat(
		"%02d:%02d:%02d",
		toInt(floor({[~]Tag}/3600)),
		toInt(floor({[~]Tag}%3600/60)),
		toInt(floor({[~]Tag}%60))
	)
)

If your float value is too large the first value will have more than 2 digits.

3 Likes

Casting to Int first and using changing f to d worked. The first value (hour) will always be less than 24 (max time will be 23:59:59 before it goes back to 0:00:00). Thank you!

Closing the loop for those as lost as I was. Here is the exact expression tag script and the outcome in both Designer and the web UI.

if(
	isNull({Tag.Value}),
		"0:00:00",
		stringFormat(
			"%d:%02d:%02d",
			toInt(floor({Tag.Value})/3600),
			toInt(floor({Tag.Value})%3600/60),
			toInt(floor({Tag.Value})%60)))

Designer:
designer-seconds-hhmmss

Web UI:
web-seconds-hhmmss

If your hour value will always be less than 24 and your value is in seconds then couldn't you do something like:

dateFormat(addSeconds(midnight(now()),{[~]Tag})'HH:mm:ss')

Perhaps, except I still need out of dateTime format which forces m/d/y on me. I started out with this approach and while in the Designer I could get the string format on the tag to show properly, in the web UI it still included the m/d/y.

This tag is only for the operator to view. Calculations/etc will always use the seconds or a dateTime format as appropriate for the function.

Thats why I used the dateFormat() function. It returns a string so you should only get back the HH:mm:ss with no m/d/y. As the spot your using this expression is set to use an expression then you should only see the HH:mm:ss.

1 Like

Pretty sure this would have issues with daylight savings, so just a word of caution there.

1 Like

I didn’t think of daylight savings but he can always use the getDate() function and set it to any date, instead of using now(), since the date is over no concern in this application.

1 Like

That is an interesting callout - the seconds I am provided are not in the context of current date/time, but rather a count up from 0 which resets either after flow status becomes true or at midnight the day of reporting. The problem may impact me on both sides - the raw tags and the conversion.

Flow time this period, I assume, would not be impacted as it is just a count of ticks (will confirm with ThermoFisher).

Flow time today and yesterday are subject to the reset at midnight. Once a year I get an extra hour of flow and once a year I lose an hour of flow. I suppose this is standard and is a ~4% swing on those days. The related data, volume and energy (which is a calculation of BTU and volume) are similarly impacted so it is probably a wash on my accounting.

I guess I need to discuss this with ThermoFisher to make sure there is no manipulation on their side to try to handle DST (their clock is programmed for it).

Sorry for the ramble, but that might be a significant concern on the reporting I will be building next. Right now we are going the MVP approach and building reports via the Status → Tags web UI (keeping the heat off of me).

If you can, my recommendation would be to see if the ThermoFisher will also output a counter that does not reset at midnight but just counts indefinitely until it hits some defined max value and resets. This way you do not have to worry about the PLC/Instrument clocks and can just worry about the Ignition/DB clocks. The aggravation and awful code I’ve had to write in the past to handle PLCs that lose their time…I find it much easier to just trend a non-resetting - or rather, infrequently resetting - counter and pull out the daily totals from the historical data. That’s just my 2 cents. I’m guessing there are others who think differently. At the very least, I would recommend setting some alarms or notifications for when there is inevitably a time mismatch between Ignition and the instrument/PLC clock.

I can't change the reset, but that doesn't mean I can't read the flow status every (reasonable) interval and create my own version of the same.

In this case, a producing gas well is shut in (flow state false) when pressure hits a low value (programmed and controlled externally, today). The well comes back online (flow state true) when pressure hits a max value.

Interesting idea... Thinking through it, there is a margin of error I need to handle in this scenario on a daily basis rather than a bi-annual. I might write another set of tags to track this scenario over time and look for any measurable drift.

1 Like

I’m typically dealing with AB PLCs that are doing all the flow totalizing for me. I make it a requirement that the PLC programmer includes a continuous totalizer and daily totalizers, with an additional tag holding the previous day’s total. I set it up so that Ignition, at a minimum, records a historical data point at midnight everyday (according to the Ignition Gateway) for the continuous totalizer and records the previous day’s total whenever that tag value changes. I also track the daily resetting total…just because, why not? :man_shrugging: The previous days total gives me a safety net in the event that the Gateway loses connection to the PLC at midnight. I solely work with Anaerobic Digestion facilities, so our flow totals don’t have to be crazy accurate anyways since it is really just for keeping an eye on system hydraulic balances.

I could see this approach introducing too much error for tracking oil production. I also would not recommend trying to count yourself since you will then have to compete with any dropped connections between Ignition and your flow status. I would only do this as a backup or a double check that you could use for triggering an alert or notification that there may be an issue with the counter.

I just recommend keeping an eye on tracking your daily counter as you could lose connection to the ThermoFisher around midnight. This is where having a tag/datapoint in the instrument/PLC that holds the previous day’s total comes to the rescue…

1 Like

All great points. My production needs and my learning/science experiments certainly don't have to be parallel. :slight_smile: Part of my work on this system is to learn how to be better at my actual job w/ Microsoft as a strategist/advisor (not actually in the weeds). The other part is to provide a minimal amount of data needed to do allocations on the sales produced by the wells via a gathering system (where the meters actually reside).

Some day soon we will be doing much more than just volume and energy accounting. Artificial lift controls and applying rocket surgery to them via machine learning capabilities on my edge devices being an example.

2 Likes

Maybe this should be another thread, but…
I’m doing something similar, adding days to the string. The expression provides the dd:hh:mm:ss format I’m looking for. All I want to do is display the System Gateway Uptime tag in a readable format. However, I have not been able to figure out why every number rounds up. For example, when the seconds tick to 00, the minute will not change until the seconds tick to 30. And this occurs for every time interval. I can only assume it’s the same for seconds as well, as I cannot see ms.

How can I lay this out… So I see:
00:00 through 00:29 and the time will update at 30 seconds to 01:30 up through 01:29 again when minutes will display 2.

Does that make sense? Anyone have any thoughts?

I'm a little confused by this bit. But the expression below works for me to convert the gateway uptime to dd:HH:mm:ss:

if(
	isNull({[System]Gateway/UptimeSeconds}),
	"00:00:00:00",
	stringFormat(
		"%02d:%02d:%02d:%02d",
		toInt(floor({[System]Gateway/UptimeSeconds} / (24 * 3600))),
		toInt(floor({[System]Gateway/UptimeSeconds} % (24 * 3600) / 3600)),
		toInt(floor({[System]Gateway/UptimeSeconds} % 3600 / 60)),
		toInt(floor({[System]Gateway/UptimeSeconds} % 60))
	)
)

Thanks for your response.
That’s exactly the expression I’m using. Here’s what I have:

The tag UptimeSeconds = 143,164 seconds
Converted should be = 01:15:46:04
The expression returns = 02:16:46:04

The seconds tick away, but the minutes have displayed 46 for the past 34 seconds, and won’t change to 47 mins until 30 seconds.
I ticked from 15 hours to 16 at 30:30, and I assume it went to 2 days at 12:30:30.

Does that help?