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.
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)
ah. gotcha. thank you. 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:
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
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.
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.
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:
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 silenceTimeStoredvalueChanged script:
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. having corrected them, this is now chugging as desired.
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 )
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)
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.