[Bug-15466,15467,15471]Add New Tag 8.0

In 8.0 there is not a system.tag.addTag function anymore, now we have to use is the system.tag.configureTag. While running this tag, i get

File "<function:runAction>", line 19, in runAction
RuntimeError: maximum recursion depth exceeded (Java StackOverflowError)

Error. Here is some of my code:

	userDS = ScriptingFunctions.jsonToDs(self.session.custom.user)
	# Configure the tag.
	tag = {
	            "name": self.session.props.id,           
	            "valueSource": "memory",
	            "value": self.session.custom.user,
	            "tagType": "DataSet"
	        }
	 
	# Set the collision policy to Abort. Thus, if a tag already exists at the base path,
	#    we will not override the tag. If you are overwriting an existing tag, then set this to "o"
	collisionPolicy = "o"
	system.tag.configure("[default]SessionIDs", tag, collisionPolicy)

The userDS variable is of DataSet type and i’m tring to make a dataset memory tag and i get that error. Can anyone point out what I’m dong wrong?

Which line of your code is #19? The first, or the last?

Oh, your tag argument needs to be a list of tags, so…

userDS = ScriptingFunctions.jsonToDs(self.session.custom.user)
	# Configure the tag.
	tag = {
	            "name": self.session.props.id,           
	            "valueSource": "memory",
	            "value": self.session.custom.user,
	            "tagType": "DataSet"
	        }
	 
	# Set the collision policy to Abort. Thus, if a tag already exists at the base path,
	#    we will not override the tag. If you are overwriting an existing tag, then set this to "o"
	collisionPolicy = "o"
	system.tag.configure("[default]SessionIDs", [tag], collisionPolicy)

Note that your tag argument is now a list with only one entry.

1 Like

I’m still getting the same error. Also, line 19 is the configureTag line

Could it have anything to do with the fact that you’re supplying the json of the user to the dataset tag, instead of the dataset you constructed in line 7?

"value": self.session.custom.user,

should probably be

"value": userDS,

Yes it should, thank you. I gave that a try and no error but not a new tag.

Did you refresh the Tag Browser?

yes

Ah, but if you look at your Gateway logs, there’s an error there. I’m currently investigating that, but it’ll be a while before I have information for you.


Logger	Time	Message
Scripting[util_tag]	15Nov2019 16:59:32	Error converting PyTagDictionary to tag edit:
java.lang.UnsupportedOperationException: JsonObject

at com.inductiveautomation.ignition.common.gson.JsonElement.getAsString(JsonElement.java:193)

at com.inductiveautomation.ignition.common.tags.config.TagGson$PropertySetTypeAdapter.deserializeJsonObject(TagGson.java:546)

at com.inductiveautomation.ignition.common.tags.config.TagGson$PropertySetTypeAdapter.deserialize(TagGson.java:517)

at com.inductiveautomation.ignition.common.tags.config.TagGson$PropertySetTypeAdapter.deserialize(TagGson.java:474)

at com.inductiveautomation.ignition.common.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:69)

at com.inductiveautomation.ignition.common.gson.Gson.fromJson(Gson.java:927)

at com.inductiveautomation.ignition.common.gson.Gson.fromJson(Gson.java:892)

at com.inductiveautomation.ignition.common.gson.Gson.fromJson(Gson.java:841)

at com.inductiveautomation.ignition.common.gson.Gson.fromJson(Gson.java:813)

at com.inductiveautomation.ignition.common.tags.TagUtilities.toTagConfiguration(TagUtilities.java:135)

at com.inductiveautomation.ignition.common.script.builtin.AbstractTagUtilities.dictToTagEdits(AbstractTagUtilities.java:183)

at com.inductiveautomation.ignition.common.script.builtin.AbstractTagUtilities.lambda$configure$0(AbstractTagUtilities.java:144)

at java.base/java.util.stream.ReferencePipeline$7$1.accept(Unknown Source)

at java.base/java.util.Iterator.forEachRemaining(Unknown Source)

at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Unknown Source)

at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source)

at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)

at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(Unknown Source)

at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source)

at java.base/java.util.stream.ReferencePipeline.collect(Unknown Source)

at com.inductiveautomation.ignition.common.script.builtin.AbstractTagUtilities.configure(AbstractTagUtilities.java:144)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.base/java.lang.reflect.Method.invoke(Unknown Source)

at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:188)

at com.inductiveautomation.ignition.common.script.ScriptManager$ReflectedInstanceFunction.__call__(ScriptManager.java:518)

at org.python.core.PyObject.__call__(PyObject.java:515)

at org.python.core.PyObject.__call__(PyObject.java:519)

at org.python.pycode._pyx21.runAction$1(:19)

at org.python.pycode._pyx21.call_function()

at org.python.core.PyTableCode.call(PyTableCode.java:171)

at org.python.core.PyBaseCode.call(PyBaseCode.java:308)

at org.python.core.PyFunction.function___call__(PyFunction.java:471)

at org.python.core.PyFunction.__call__(PyFunction.java:466)

at org.python.core.PyFunction.__call__(PyFunction.java:456)

at org.python.core.PyFunction.__call__(PyFunction.java:451)

at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:788)

at com.inductiveautomation.ignition.common.script.ScriptManager$ScriptFunctionImpl.invoke(ScriptManager.java:917)

at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$AutoRecompilingScriptFunction.invoke(ProjectScriptLifecycle.java:626)

at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invoke(ScriptFunctionHelper.java:91)

at com.inductiveautomation.perspective.gateway.action.ScriptAction.runAction(ScriptAction.java:71)

at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.lambda$call$0(ActionCollection.java:263)

at com.inductiveautomation.perspective.gateway.api.LoggingContext.mdc(LoggingContext.java:54)

at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.call(ActionCollection.java:252)

at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.call(ActionCollection.java:221)

at com.inductiveautomation.perspective.gateway.threading.BlockingTaskQueue$TaskWrapper.run(BlockingTaskQueue.java:154)

at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)

at java.base/java.util.concurrent.FutureTask.run(Unknown Source)

at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)


Yes, that’s what I encountered when attempting to replicate your issue. I opened a ticket for it, but it’ll be some time before a dev is able to investigate it.

I actually opened three different tickets during my investigation, but I have no idea how long it will take for them to be investigated by a developer.

1 Like

The following will work in creating a dataset tag with a dataset value:

	userDS = '{"columns":[{"name":"Int","type":"java.lang.Integer"},{"name":"Str","type":"java.lang.String"}],"rows":[[123,"ABC"]]}'
	# Configure the tag.
	tag = {
				"name": self.session.props.id,           
				"valueSource": "memory",
				"value": userDS,
				"tagType": "AtomicTag",
				"dataType": "DataSet"
			}
	 
	# Set the collision policy to Abort. Thus, if a tag already exists at the base path,
	#    we will not override the tag. If you are overwriting an existing tag, then set this to "o"
	collisionPolicy = "o"
	system.tag.configure("[default]SessionIDs", [tag], collisionPolicy)

The problem is going to be getting the value in JSON string form from a custom field. We are returning a wrapper object that doesn’t like to return a proper string version of the JSON we need for the value to be accepted. One of the tickets Cody has opened hopefully will deal with that, but for now you would need to build the JSON string yourself. If I get some free time I will see if I can figure out something useful, but wanted you to at least get to a point where the tag is getting created.

I am not sure if you know this already, but this is a useful hint for system.tag.configure. If you need to know what properties to define or the what values are accepted, creating an example tag via the Designer UI and looking at the Edit (raw) dialog can help you get the values you need.

Garth

1 Like

I have done this as you have there. It creates the tag and gets a value datatype of Dataset but the value is always empty.

This typically happens when the data being sent isn’t in a format that the Gateway can determine. Make sure that the string you are providing can be parsed into JSON properly.

A developer, Cody and myself worked this morning to get custom objects to convert to JSON. The attached example will create a dataset tag from the my_obj custom property on the button itself. The python code relies on a function to recursively build the dictionary that then is converted into a JSON object and then converted into a string. It works, but we need to provide a more elegant solution. The code for pulling the property is:

	def recurse(wrapper):
	            if hasattr(wrapper, 'keys'):
	                return {key: recurse(value) for key, value in wrapper.items()}
	            elif hasattr(wrapper, 'index') and not isinstance(wrapper, basestring):
	                return [recurse(value) for value in wrapper]
	            else:
	                return wrapper
	
	userDS = self.custom.my_obj
	userDS_json = system.util.jsonEncode(recurse(userDS))

	# Configure the tag.
	tag = {
				"name": self.session.props.id,           
				"valueSource": "memory",
				"value": str(userDS_json),
				"tagType": "AtomicTag",
				"dataType": "DataSet"
			}
	 
	# Set the collision policy to Abort. Thus, if a tag already exists at the base path,
	#    we will not override the tag. If you are overwriting an existing tag, then set this to "o"
	collisionPolicy = "o"
	system.tag.configure("[default]SessionIDs", [tag], collisionPolicy)

A working button example is here:
CreateTagExample.json (1.8 KB)

1 Like

Thank you for your help. That made it work.

@ggross @cmallonee Can you comment whether these bugs apply to my situation?

I have a script that generates a python list with dictionaries (multiple levels). When I output this object and bind that to a “label” component for example, the result is a valid JSON string. The same works when binding directly to the “items” property of a Perspective MenuTree component.

I am now trying to feed the output to a Document-type memory tag by using system.util.jsonEncode() on my python object, but am getting the maximum recursion depth error.

Below is a JSON representation of an object that works with jsonEncode() and one that breaks it. It appears the issue is that jsonEncode() can’t handle the depth of the object properly:

Works with jsonEncode()

[{"visible":"true","showHeader":"true","navIcon":{"path":"material/chevron_right","color":"#6C6C6C"},"style":{"classes":""},"label":{"icon":{"path":"material/domain"},"text":"Plant1"},"items":[{"visible":"true","showHeader":"true","navIcon":{"path":"","color":"#6C6C6C"},"style":{"classes":""},"label":{"icon":{"path":"material/place"},"text":"Line1"},"items":[],"enabled":"true","target":""}],"enabled":"true","target":""}]

image

Breaks jsonEncode()

[{"visible":"true","showHeader":"true","navIcon":{"path":"material/chevron_right","color":"#6C6C6C"},"style":{"classes":""},"label":{"icon":{"path":"material/domain"},"text":"Plant1"},"items":[{"visible":"true","showHeader":"true","navIcon":{"path":"material/chevron_right","color":"#6C6C6C"},"style":{"classes":""},"label":{"icon":{"path":"material/place"},"text":"Line1"},"items":[{"visible":true,"showHeader":true,"navIcon":{"path":"material/chevron_right","color":"#6C6C6C"},"style":{"classes":""},"label":{"icon":{"path":"material/folder_open"},"text":"7xx - "},"items":[{"visible":true,"showHeader":true,"navIcon":{"path":"material/wifi_off","color":"#6C6C6C"},"stationTagRoot":{"fullPath":"[default]Test/Plant1/Line1/701","hasChildren":false,"name":"701","tagType":"Folder"},"style":{"classes":""},"label":{"icon":{"path":""},"text":"701 - Disconnected"},"items":[],"enabled":true,"target":""}],"enabled":true,"target":""}],"enabled":"true","target":""}],"enabled":"true","target":""}]

image

It’s difficult to say because you haven’t provided the stacktrace of your error, nor have you supplied the version you are currently using. The RuntimeError: maximum recursion depth exceeded (Java StackOverflowError) issue should have been fixed in 8.0.7, so if you’re using a version newer than 8.0.7 and you’re encountering the same error then this should be further investigated.

I was able to take the value you supplied which you specified as breaking jsonEncode and I was able to get it to work with the following script:

data = [{"visible":"true","showHeader":"true","navIcon":{"path":"material/chevron_right","color":"#6C6C6C"},"style":{"classes":""},"label":{"icon":{"path":"material/domain"},"text":"Plant1"},"items":[{"visible":"true","showHeader":"true","navIcon":{"path":"material/chevron_right","color":"#6C6C6C"},"style":{"classes":""},"label":{"icon":{"path":"material/place"},"text":"Line1"},"items":[{"visible":True,"showHeader":True,"navIcon":{"path":"material/chevron_right","color":"#6C6C6C"},"style":{"classes":""},"label":{"icon":{"path":"material/folder_open"},"text":"7xx - "},"items":[{"visible":True,"showHeader":True,"navIcon":{"path":"material/wifi_off","color":"#6C6C6C"},"stationTagRoot":{"fullPath":"[default]Test/Plant1/Line1/701","hasChildren":False,"name":"701","tagType":"Folder"},"style":{"classes":""},"label":{"icon":{"path":""},"text":"701 - Disconnected"},"items":[],"enabled":True,"target":""}],"enabled":True,"target":""}],"enabled":"true","target":""}],"enabled":"true","target":""}]
system.tag.writeBlocking(['[default]Experimentation/Doc'],[system.util.jsonEncode(data)])

Please note that there were invalid values in the “json” you provided; true and false are not proper booleans in Python - they are interpreted as variables which had not been defined (variables with names true and false). Some of your values "true" and "false" are valid string values, so I left those unchanged under the assumption you’re attempting to store string representations of a boolean value. I changed the offending values to proper boolean values (True and False), and then I had no issue.

Thanks for looking into this. I have a fairly complicated script, but I have narrowed it down to an issue with converting a tag object into JSON. I am running 8.0.12

Given the following transform script bound to a Label component, and given that there exists a folder tag named “Plant1” under [default]Test:

	rootTag = "[default]Test"
	plantTags = system.tag.browse(rootTag).getResults()
	for tag in plantTags:
		item = {'tagRoot':tag}
		return item
		#return system.util.jsonEncode(item)

this appears in the preview output of the script:
image

and this appears the label component:
{"tagRoot":{"fullPath":"[default]Test/Plant1","hasChildren":true,"name":"Plant1","tagType":"Folder"}}

Note, that for now, I’m passing the item object, not JSON, and it appears that the label is converting to JSON for me.

Now, when I change the return command to “return system.util.jsonEncode(item)”, I get the error:
image

It looks like the reason you’re encountering this is because the returned value from getResults() is a Java Collection (meaning it is a Java object with Java-type variables - like true - instead of Python-type variables like True). So while jsonEncode expects a Python dictionary, you’re sending it a Java Collection. The scripting environment you’re using is Jython, which does the best it can to mimic and/or cast variables as it encounters them, but you’re using jsonEncode which is acting as a translator of sorts; it can only translate Python - but as I said - you’re giving it Java.

The Label is working because it’s just wrapping/casting the value as a String behind the scenes.

There’s some discussion here over the best way to handle what you’re trying to do. While this behavior is probably not ideal, we might be able to provide a better way of doing this in the first place if you clarify what you’re trying to accomplish. If you’re just looking for Tag values in JSON form, you could bind against a directory like so:
Screen Shot 2020-06-05 at 8.18.20 AM