Set one tag value to another tag's value on valueChanged event

we have a dataset tag whose value is from an MQTT broker. power goes out, value lost. we need to set a stored value and re-write it to the tag when it comes back online. we ALSO need to set it when the user changes the value manually. so i am thinking of targeting the onValueChange value event on the 'stored' dataset to force a write back to the MQTT tag:

def valueChanged(blah-blah):    #  i copied baseline composition from existing stuff
    if '[.]Online':  #<--- the tag at the 'base' level of the Tag list
        '[.]../../silenceTime'.value = currentValue  #<--- both this tag and 
            # the tag this is bound to are at the same nested Tag list level

is this a sound strategy or is there a better way?

EDIT: i learned some stuff and am rethinking this. instead of scripting either of the two tags, i am going to script the 'Online' tag's 'valueChange' event instead. which is actually more accurate. so currently, it looks like this:

def valueChanged(blah-blah):
    if not currentValue:    #<--- this is a boolean: online --> yes/no
        '[.]../../silenceTime'.value = '[.]../../silenceTimeStored'.value

so when the unit comes back online, boom; data gets written across.

is this good or is there a better way yet?

Be sure to filter against the initialChange flag, or this script will execute anytime the project is saved (or for any other event that causes a resubscription).

Also, currentValue is a qualifiedValue so you'll want to check the value property otherwise, this would actually execute on every value change.

def valueChange(blah-blah):
    if not any(initialChange,currentValue.value):
        system.tag.writeAsync(['[.]../../silenceTime'],system.tag.readBlocking(['[.]../../silenceTimeStored'])[0].value)

okay. excellent! thank you! quick final question: why is initialChange on it's own, but currentValue requires .value appended?

initialChange is a bare boolean. currentValue is a QualifiedValue with value, quality, and timestamp properties.

2 Likes

ah. gotcha. thank you. :slight_smile: and, again, for my OCD brain: i cannot directly address a value, aka '[.]Online'.value, i have to use system.tag.read(or)readBlocking('[.]Online')[0]).value

EDIT: okay. i flight-tested this as best i could. here's the structure:
Screenshot 2023-07-20 154436

i placed the valueChanged script from @lrose on the Online tag, and another on the silenceTimeStored tag. when i change either of the two values... nothing. what is going on?

Online `valueChanged script:

def valueChanged(tag, tagPath, previousValue, currentValue, initialChange, missedEvents):
	#
	#    when the panel comes back online, write out the values to the MQTT Broker
	#
	if currentValue.value or not initialChange:
		print('------------->  online!')
		system.tag.writeAsync(['[.]silenceTime'], system.tag.readBlocking(['[.]../../silenceTimeStored'])[0].value)
	else:
		print('offline!  <-------------')

the silenceTimerStored valueChanged script:

def valueChanged(tag, tagPath, previousValue, currentValue, initialChange, missedEvents):
	if readBlocking(['[.]../../silenceTimeStored'])[0].value:
		system.tag.writeAsync(['[.]silenceTime'], system.tag.readBlocking(['[.]../../silenceTimeStored'])[0].value), system.tag.readBlocking(['[.]../../silenceTimeStored'])[0].value)
	else:
		system.tag.writeAsync(['[.]silenceTime'],'OFFLINE')  #<--- for testing feedback purposes

In a script, you can not directly address a value.

This: '[.]Online' is just a string.

Technically, system.tag.read() will work but it's deprecated.

There are two other functions you can use to read tag values depending on if you need it to block or not.

system.tag.readBlocking()
system.tag.readAsync()

And they return a List of qualifiedValues so you must index into them as such and access the value property.

This post will give you a quick history lesson on this, if you're interested.

1 Like

Where are you looking for the print out?

Output Console in Designer. but that's the least of my concerns. the scripts aren't firing on value change. period. i should see those changes in the tag browser.

Tag change scripts execute in the gateway scope. Print will output to the Wrapper log.

i also have to stop editing my earlier post with updated content. that's an old habit from another forum. sorry! :stuck_out_tongue:

1 Like

I agree, unless something isn't the value that you think it is. If you look in the wrapper log and see 'offline! <-------------' that will give you some info.

hell, at this point, i'll take any value! :slight_smile: as long as it changes. then work from there.

i have to punch out, but let's pick this up tomorrow. i really need to nail this down. for work and for sanity. :smiley: thanks for your help so far.

Based on your screenshot, your relative tag paths are incorrect. You're showing the tags are at the same nesting level within the UDT yet you have [.]silenceTime and [.]../../silenceTimeStored. Should be:

system.tag.writeAsync(['[.]silenceTime'], system.tag.readBlocking(['[.]silenceTimeStored'])[0].value)

Is the silenceTime memory tag an integer or string datatype? If it's an integer, this line will fail:

Looks like you accidentally called system.tag.readBlocking(['[.]../../silenceTimeStored'])[0].value) twice in your silenceTimeStored valueChanged script:

2 Likes

the copy/paste monkeys did me no favours, for sure. i was cobbling this together from three sources. regardless, you're correct: the pathing was wrong; they were all on the same level.

also: i keep forgetting the 'Java' part of Jython: strictly typed vars. yes: OFFLINE will not work. thanks for that catch.

so thank you for fixing all my slapdash errors. :+1: having corrected them, this is now chugging as desired. :100:

1 Like

I noticed this one after the others I sent and didn't want to pile on all at once, but you're also missing system.tag. for calling the readBlocking() function. (Couldn't not say something...had to scratch that itch :sweat_smile:)

2 Likes

I will also say, that (especially with tag value change scripts) you should always read (and write for that matter) as many tags as possible in a single go. The readBlocking() accepts and returns a list for exactly this reason. It is exponentially faster to read multiple tags at once then to execute multiple reads.

The reason is that all tag valueChange events execute in a deditcated thread pool of size 3, IIRC. This means that long running scripts have the opportunity to exhaust the thread pool, leading to missed events. @pturmel recommends that any tag valueChange script execute in < 100ms, ideally in the tens of ms, I believe. This is why generally it is recommended to utilize Gateway Tag Change Events which do not have this limitation.

I use this script to put all of the tags into a dictionary for simple readable access.

def createTabValueDict(tagPaths,parentPath):
    return {tagName[len(parentPath):]:qValue.value for tagName,qValue in zip(tagPaths,systemtag.readBlocking(tagPaths))}

I use this script to write the values back.

def writeTagValueDict(values,parentPath):
    parentPath = parentPath if parentPath[-1:] == '/' else parentPath + '/'
    writePaths, writeValues = [list(x) for x in zip(*values.items())]
    longWritePaths = [parentPath + path for path in writePaths]'
    return system.tag.writeBlocking(longWritePaths,writeValues)

Utilize them like this:

tagValues = createTagValueDict(['path/to/tag1','path/to/tag2'],'path/to/')

tag1Value = tagValues['tag1']
tagValues['tag2'] = 'new value'

writeTagValueDict(tagValues,'path/to/')
2 Likes

Close.

  • I recommend that all events, gateway and UI, unless carefully set up to only handle one somewhat rare condition, execute in an average of 100ms or less. An occasional excursion is generally tolerable. Humans are particularly sensitive to responses greater than 0.1 second.

  • I recommend that tag events, defined on a tag, not in a project, run in single digit milliseconds, and no excursions. If a large system (tens of thousands of tags with scripts), even less than that, or use the setting in ignition.conf to change the thread pool.

This means no database operations in tag events. NONE. And no network operations--no web API calls and no access to network file systems. (People forget that one.) No blocking tag reads to remote tag providers, and no blocking OPC tag writes or blocking tag writes to remote tag providers.

5 Likes

@pturmel how would i clock those calls? it could explain some of the bog we get...

besides time.time()... :smiley: unless that's literally the answer...

Somewhere in a project script module that is included in your Gateway Scripting Project, include:

from java.lang import System

That makes it possible, in your tag events, to use something like this:

def valueChanged(....):
	start_ts = gwscript.System.nanoTime()
	.....
	... Do work
	.....
	nanos = gwscript.System.nanoTime() - start_ts
	if nanos > 10000000L:
		system.util.getLogger("TagEventExcursions").warnf("Tag Event *somename* Overtime! %dns", nanos)

Then filter (or grep) your gateway log to see these.

Java's System.nanoTime() is made specifically for this kind of duty--efficient, and tightly synchronized across multiple cores in a system.

3 Likes