Counter like tag with cumulated value

Hi,

I am currently recording the live status of the machine and displaying the status of the machine as below,
image
Whenever the machine is in Stopped state for more than 10 mins, I would like to start a counter and display as 'hh:mm:ss' in the next column ( for every shift as shift loss)
And when the machine goes from Stopped to any other state, the duration in the loss column must be halted and whenever the machine goes back to stopped state, the cumulated time should be displayed.

Moreover whenever the shift changes, this time should reset and if the machine is already in stopped state, counter should start from one,

Currently i am following the below approach,

Tags used:

  1. State tag
  2. Statechange time elapsed (to display the duration )
  3. Global Shift change tag

Calculation:

  1. LossFlag (Expression Tag)
if ({[.]../StateChange}="Stopped",true,false)

2.ShiftLossCumulative Tag:

if ( ({[.]ShiftLoss} = true ) && ({[.]../StateChangeTimeElapsed}>600), {[.]ShiftLossCumulative}+1, 0)

Tag change event script:

vShift = system.tag.readBlocking("[~]BASE/Global/ShiftChange")[0].value
	
	if vShift == True:
		currentValue.value = 0
	
	elif currentValue.value == 0 and previousValue !=0:	
		currentValue.value =currentValue.value + previousValue.value

3.ShiftLossMins:

if(
            isNull({[.]ShiftLossCumulative}),
            "00:00:00",
    if ({[.]../StateChange}="Stopped" && {[.]../StateChangeTimeElapsed} > 600 && {[default]BASE/Global/ShiftChange}=FALSE,
                        
                                stringFormat(
                                            "%02d:%02d:%02d",
                                            toInt(floor(({[.]ShiftLossCumulative}+600)/3600)),
                                            toInt(floor(({[.]ShiftLossCumulative}+600)%3600/60)),
                                            toInt(floor(({[.]ShiftLossCumulative}+600)%60))),
            
                                
                    stringFormat(
                                    "%02d:%02d:%02d",
                                    toInt(floor({[.]ShiftLossCumulative}/3600)),
                                    toInt(floor({[.]ShiftLossCumulative}%3600/60)),
                                    toInt(floor({[.]ShiftLossCumulative}%60))
                    )
                    
            )
    )

This 'ShiftLossMins' Tag will be displayed in the screen.

Somehow, this is getting failed and occuring issues like, when the machine is in stopped state it is working fine.
When it goes to other state, ShiftLossMins getting reset

Any suggestions to overcome this issue?

Are you attempting this from a client level tag change event script?
Is it possible to do the downtime calculations from the machine PLCs?

Let's start with a few things that should make things simpler, and which might just point to errors in the logic:

  • You don't need to do if (boolean, true, false). boolean is enough:
    => LossFlag: {[.]../StateChange} = "Stopped"
  • The same goes for if ( ({[.]ShiftLoss} = true) ...). If something = true is true, it means that something is... already true.
  • if vShift == True. Same thing, if vShift is enough.
    You set currentValue.value to 0 here, but you're not using it afterward, so the whole if statement does nothing except running a starting clause for the else that follows. If you want to update the tag, you need to use system.tag.writeBlocking.
  • In the else condition, what is previousValue != 0 supposed to do ? Did you want to compare it to None instead to make sure previousValue does exist, or did you mean to check its value ?
    If you want to make sure it exists, use previousValue is not None. If you want to check for its value, then you don't need to do it at all. The only thing previousValue.value is used for is an addition. If its value is 0, then adding it to something will give you the same result.
  • In ShiftLossMins expression, you're using {[.]..{StateChange}="Stopped"}. You already did that comparison for LossFlag. Use this instead.
    I also suggest you don't format it there. Just return the time (number of seconds, minutes, whatever is the most appropriate, or even use a datetime tag), and format it where you display it:
if(isNull({[.]ShiftLossCumulative}),
	0,
	if ({[.]LossFlag} && {[.]../StateChangeTimeElapsed} > 600 && {[default]BASE/Global/ShiftChange} = false,
		{[.]ShiftLossCumulative}+600,
		{[.]ShiftLossCumulative}
	)
)

Which could be 'simplified' further but let's not go there yet.

For now, I'd take a look at the tag change script, and make sure the logic there is sound.

1 Like

Actually, I'm not sure about that. I don't use tag change scripts on tags, only gateway events... So maybe currentValue here actually updates the tag.

That being said, is that script on the tag ShiftLossCumulative ?
To me, the trigger seems to be the shift change instead, so I'd put it there.

Hi
I am getting Error_Expression_Val in ShiftLossCumulative,ShiftlossInMins tags

Attempting from tag change event script

Without seeing them, there's not much I can tell you...

So, I'm having a bit of fun trying to make this work with expression tags only, but it's a bit trickier that I thought.
This part particularly:

Can you tell me more about the shift change tag ? Does it go to true for a few seconds then go back to false ? Does it stay on longer ? When do we start counting again, just as it becomes true, or when it goes back to false ?

For now, I have this:
image

  • lossFlag is just a memory boolean tag, that I toggle on and off manually.
    For you, it should be an expression tag using whatever machine state tag you want to evaluate.

  • currentStateTimer counts the time between now and lossFlag's timestamp

secondsBetween({[.]lossFlag.timestamp}, now()) * toInt({[.]lossFlag})
  • previousLoss stores the time that has been lost so far, without the current timer
if (!{[.]lossFlag},
	{[.]shiftLoss},
	coalesce({[.]previousLoss}, 0)
) * toInt(!{[.]../shiftChange})
  • shiftLoss is the total of previousLoss and currentTimer
coalesce({[.]previousLoss}, 0) + {[.]currentStateTimer}

When lossFlag goes to true, currentTimer starts counting, which also increment shiftLoss.
When lossFlag goes back to false, currentTimer goes to 0, shiftLoss stops incrementing, and previousLoss takes the value of shiftLoss.

Now, the reset part will depend on the information you can give me about shiftChange

Note the coalesce calls in the expressions: they're here to ensure an initial value when creating the folders (I just copy/pasted the folder a few times to test it).
The one in previousLoss expression is not strictly necessary, it works fine without it, but it prevents an expression eval error when initially creating the folder (the tag tries to get its own value, which doesn't work as it doesn't have one yet).

1 Like

I have defined a custom tag called Shift and Whenever Shift changes it goes to True
And after few secs it goes false
Shift change is a tag that refers this one

image
image
For me, Previous Loss is not getting stored

Then using this expression:
{[.]machineState} = "stopped" && !{[.]../shiftChange}
for lossFlag will make it false when shiftChange becomes true, then go back to its previous state when shiftChange goes back to false, with every counter reseted to 0.

Here's the json of my Test folder, you can copy and paste it in your tag browser

click for json
{
  "name": "Test",
  "tagType": "Folder",
  "tags": [
    {
      "name": "Foo",
      "tagType": "Folder",
      "tags": [
        {
          "valueSource": "expr",
          "expression": "{[.]machineState} \u003d \"stopped\" \u0026\u0026 !{[.]../shiftChange}",
          "dataType": "Boolean",
          "name": "lossFlag",
          "value": false,
          "tagType": "AtomicTag"
        },
        {
          "valueSource": "expr",
          "expression": "secondsBetween({[.]lossFlag.timestamp}, now()) * toInt({[.]lossFlag})",
          "name": "currentStateTimer",
          "tagType": "AtomicTag"
        },
        {
          "valueSource": "expr",
          "expression": "if (!{[.]lossFlag},\r\n\t{[.]shiftLoss},\r\n\tcoalesce({[.]previousLoss}, 0)\r\n) * toInt(!{[.]../shiftChange})",
          "name": "previousLoss",
          "value": 0,
          "tagType": "AtomicTag"
        },
        {
          "valueSource": "memory",
          "dataType": "String",
          "name": "machineState",
          "value": "active",
          "tagType": "AtomicTag"
        },
        {
          "valueSource": "expr",
          "expression": "coalesce({[.]previousLoss}, 0) + {[.]currentStateTimer}",
          "name": "shiftLoss",
          "value": 227,
          "tagType": "AtomicTag"
        }
      ]
    },
    {
      "valueSource": "memory",
      "dataType": "Boolean",
      "name": "shiftChange",
      "value": false,
      "tagType": "AtomicTag"
    }
  ]
}

Note that it's probably not the best/simplest/easiest way of doing this. I just wanted to try and see if I could do it with expression tags only.

1 Like

The reliable way to do this is to record state changes in an event table. Then use LEAD or LAG functions in the DB to get intervals per state, clipping to the start of shift. Sum those intervals while grouping by state. With an index on t_stamp, your DB will calculate this blisteringly fast.

Select state, sum(event_duration) as duration
From (
  Select state, nextTS - clipped as event_duration
  From (
    Select state
      , case t_stamp < :shift_start_ts then :shift_start_ts else t_stamp end as clipped
      , coalesce(lead(t_stamp) over (Order by t_stamp), current_timestamp) as nextTS
    From state_events
    Where t_stamp >= (Select max(t_stamp) Where t_stamp < :shift_start_ts)
  ) intervals
) durations

Display the final duration from the row that matches the current state.

1 Like

You also have the option to use tag history