Automation Professionals' Integration Toolkit Module

Here you go:

ToolkitRPCImpl

03Feb2026 14:09:46
Unexpected error delegating to toolkitTest.gwLogInfof
java.util.NoSuchElementException: No value present

at java.base/java.util.Optional.orElseThrow(Unknown Source)

at com.automation_pros.simaids.gateway.ToolkitRPCImpl.runInGateway(ToolkitRPCImpl.java:77)

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 com.inductiveautomation.ignition.gateway.rpc.RpcDelegate$DelegateRpcHandler.handle(RpcDelegate.java:275)

at com.inductiveautomation.ignition.gateway.rpc.RpcRoutes.lambda$handle$1(RpcRoutes.java:209)

at com.inductiveautomation.ignition.gateway.rpc.RpcRoutes.safelyHandle(RpcRoutes.java:225)

at com.inductiveautomation.ignition.gateway.rpc.RpcRoutes.handle(RpcRoutes.java:209)

at com.inductiveautomation.ignition.gateway.dataroutes.Route.service(Route.java:355)

at com.inductiveautomation.ignition.gateway.dataroutes.RouteGroupImpl.service(RouteGroupImpl.java:117)

at com.inductiveautomation.ignition.gateway.dataroutes.RouteGroupCollectionServlet.serviceInternal(RouteGroupCollectionServlet.java:152)

at com.inductiveautomation.ignition.gateway.dataroutes.AbstractRouteGroupServlet.service(AbstractRouteGroupServlet.java:40)

at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)

at org.eclipse.jetty.ee10.servlet.ServletHolder$NotAsync.service(ServletHolder.java:1385)

at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:751)

at org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1622)

at com.inductiveautomation.catapult.filters.GatewayFilter.doFilter(GatewayFilter.java:119)

at jakarta.servlet.http.HttpFilter.doFilter(HttpFilter.java:97)

at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)

at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)

at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1555)

at org.eclipse.jetty.ee10.servlet.ServletChannel.dispatch(ServletChannel.java:823)

at org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:440)

at org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:470)

at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:575)

at org.eclipse.jetty.ee10.servlet.SessionHandler.handle(SessionHandler.java:717)

at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1071)

at org.eclipse.jetty.rewrite.handler.RewriteHandler$LastRuleHandler.handle(RewriteHandler.java:159)

at org.eclipse.jetty.rewrite.handler.Rule$Handler.handle(Rule.java:108)

at org.eclipse.jetty.rewrite.handler.HeaderPatternRule$1.handle(HeaderPatternRule.java:89)

at org.eclipse.jetty.rewrite.handler.Rule$Handler.handle(Rule.java:108)

at org.eclipse.jetty.rewrite.handler.HeaderPatternRule$1.handle(HeaderPatternRule.java:89)

at org.eclipse.jetty.rewrite.handler.Rule$Handler.handle(Rule.java:108)

at org.eclipse.jetty.rewrite.handler.HeaderPatternRule$1.handle(HeaderPatternRule.java:89)

at org.eclipse.jetty.rewrite.handler.Rule$Handler.handle(Rule.java:108)

at org.eclipse.jetty.rewrite.handler.HeaderPatternRule$1.handle(HeaderPatternRule.java:89)

at org.eclipse.jetty.rewrite.handler.Rule$Handler.handle(Rule.java:108)

at org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:143)

at org.eclipse.jetty.server.Handler$Sequence.handle(Handler.java:805)

at org.eclipse.jetty.server.Handler$Sequence.handle(Handler.java:805)

at org.eclipse.jetty.server.Server.handle(Server.java:182)

at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:677)

at org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:416)

at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)

at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)

at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)

at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:480)

at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:443)

at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:293)

at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.run(AdaptiveExecutionStrategy.java:201)

at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:311)

at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:981)

at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1211)

at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1166)

at java.base/java.lang.Thread.run(Unknown Source)

Huh. Not helpful. And seems I never properly published v2.1.2... Coming up.

For Ignition v8.3+: v2.1.2.253151852

I found this in December, and queued it up, but didn't push. ):

(v2.1.1 doesn't have the ClientReqSession material at all.)

1 Like

My example works with v2.1.2 :+1:
Thanks!

1 Like

Is there room for improvement with how lists are coerced back into PyObjects?

# Script Module (toolkitTest)
@system.util.runInGateway
def testReturnList():
	myList = []
	return myList

# Script Console
toolkitTest.testReturnList()
Java Traceback:
Traceback (most recent call last):
  File "<input>", line 3, in <module>
java.lang.ClassCastException: class java.util.Collections$UnmodifiableRandomAccessList cannot be cast to class org.python.core.PyObject (java.util.Collections$UnmodifiableRandomAccessList is in module java.base of loader 'bootstrap'; org.python.core.PyObject is in unnamed module of loader java.net.URLClassLoader @7c2bfbb3)

	at jdk.proxy2/jdk.proxy2.$Proxy86.runInGateway(Unknown Source)

	at com.automation_pros.simaids.client.script.RunInGateway$AsGatewayFunction.__gateway_call__(RunInGateway.java:51)

	at com.automation_pros.simaids.client.script.RunInGateway$AsGatewayFunction.__call__(RunInGateway.java:41)

	at com.automation_pros.simaids.client.script.RunInGateway$AsGatewayFunction.__call__(RunInGateway.java:56)

	at org.python.pycode._pyx65.f$0(<input>:3)

	at org.python.pycode._pyx65.call_function(<input>)

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

	at org.python.core.PyCode.call(PyCode.java:18)

	at org.python.core.Py.runCode(Py.java:1703)

	at org.python.core.Py.exec(Py.java:1747)

	at org.python.util.PythonInterpreter.exec(PythonInterpreter.java:277)

	at org.python.util.InteractiveInterpreter.runcode(InteractiveInterpreter.java:130)

	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:639)

	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:627)

	at java.desktop/javax.swing.SwingWorker$1.call(Unknown Source)

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

	at java.desktop/javax.swing.SwingWorker.run(Unknown Source)

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

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

	at java.base/java.lang.Thread.run(Unknown Source)

java.lang.ClassCastException: java.lang.ClassCastException: class java.util.Collections$UnmodifiableRandomAccessList cannot be cast to class org.python.core.PyObject (java.util.Collections$UnmodifiableRandomAccessList is in module java.base of loader 'bootstrap'; org.python.core.PyObject is in unnamed module of loader java.net.URLClassLoader @7c2bfbb3)


	at org.python.core.Py.JavaError(Py.java:545)

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

	at org.python.core.PyCode.call(PyCode.java:18)

	at org.python.core.Py.runCode(Py.java:1703)

	at org.python.core.Py.exec(Py.java:1747)

	at org.python.util.PythonInterpreter.exec(PythonInterpreter.java:277)

	at org.python.util.InteractiveInterpreter.runcode(InteractiveInterpreter.java:130)

	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:639)

	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:627)

	at java.desktop/javax.swing.SwingWorker$1.call(Unknown Source)

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

	at java.desktop/javax.swing.SwingWorker.run(Unknown Source)

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

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

	at java.base/java.lang.Thread.run(Unknown Source)

Caused by: java.lang.ClassCastException: class java.util.Collections$UnmodifiableRandomAccessList cannot be cast to class org.python.core.PyObject (java.util.Collections$UnmodifiableRandomAccessList is in module java.base of loader 'bootstrap'; org.python.core.PyObject is in unnamed module of loader java.net.URLClassLoader @7c2bfbb3)

	at jdk.proxy2/jdk.proxy2.$Proxy86.runInGateway(Unknown Source)

	at com.automation_pros.simaids.client.script.RunInGateway$AsGatewayFunction.__gateway_call__(RunInGateway.java:51)

	at com.automation_pros.simaids.client.script.RunInGateway$AsGatewayFunction.__call__(RunInGateway.java:41)

	at com.automation_pros.simaids.client.script.RunInGateway$AsGatewayFunction.__call__(RunInGateway.java:56)

	at org.python.pycode._pyx65.f$0(<input>:3)

	at org.python.pycode._pyx65.call_function(<input>)

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

	... 13 more

Traceback (most recent call last):
  File "<input>", line 3, in <module>
java.lang.ClassCastException: class java.util.Collections$UnmodifiableRandomAccessList cannot be cast to class org.python.core.PyObject (java.util.Collections$UnmodifiableRandomAccessList is in module java.base of loader 'bootstrap'; org.python.core.PyObject is in unnamed module of loader java.net.URLClassLoader @7c2bfbb3)

	at jdk.proxy2/jdk.proxy2.$Proxy86.runInGateway(Unknown Source)

	at com.automation_pros.simaids.client.script.RunInGateway$AsGatewayFunction.__gateway_call__(RunInGateway.java:51)

	at com.automation_pros.simaids.client.script.RunInGateway$AsGatewayFunction.__call__(RunInGateway.java:41)

	at com.automation_pros.simaids.client.script.RunInGateway$AsGatewayFunction.__call__(RunInGateway.java:56)

	at org.python.pycode._pyx65.f$0(<input>:3)

	at org.python.pycode._pyx65.call_function(<input>)

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

	at org.python.core.PyCode.call(PyCode.java:18)

	at org.python.core.Py.runCode(Py.java:1703)

	at org.python.core.Py.exec(Py.java:1747)

	at org.python.util.PythonInterpreter.exec(PythonInterpreter.java:277)

	at org.python.util.InteractiveInterpreter.runcode(InteractiveInterpreter.java:130)

	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:639)

	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:627)

	at java.desktop/javax.swing.SwingWorker$1.call(Unknown Source)

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

	at java.desktop/javax.swing.SwingWorker.run(Unknown Source)

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

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

	at java.base/java.lang.Thread.run(Unknown Source)

java.lang.ClassCastException: java.lang.ClassCastException: class java.util.Collections$UnmodifiableRandomAccessList cannot be cast to class org.python.core.PyObject (java.util.Collections$UnmodifiableRandomAccessList is in module java.base of loader 'bootstrap'; org.python.core.PyObject is in unnamed module of loader java.net.URLClassLoader @7c2bfbb3)

The inevitable pain of (de)serialization is part of the reason I have been so hesitant to do something like this. Looks like Phil's using the base proto based RPC serialization in 8.3, which hands back real (unmodifiable) List instances when given one over RPC, instead of any wrapped Jython thing.

Will investigate.

1 Like

Automation Professionals is pleased to announce new Production Releases of this module with the following new utility functions:

  • system.util.runInClient()
  • rex()
  • linesOf()
  • parseJTime()
  • formatJTime()

For Ignition v8.3: v2.1.4.260821836

For Ignition v8.1: v2.1.4.260821551

I do not have a built-in solution to Ben's list type issue, but I suspect it would be mitigated if the target function for runInGateway used my system.util.unQualify() function to re-wrap the return value.

Edit: Also, the expression functions that return lists now return instances of PyList instead of ArrayList, to make them more friendly to Document tags and Vision Document properties.

8 Likes

I'm trying to walk through a json using a tag path, and then returning the last found key. I can do this easily in a python script, but I'm trying to do it using an expression.

This is how I'm doing it in python:


def getAlarmNode(alarmDoc, path):
    parts = path.split('/')
    node = alarmDoc
    for part in parts:
        node = node.get(part)
        if node is None:
            return None
    return node

The alarmDoc is a document tag representing a dictionary of alarms and counts in an area.
IE

{
	'default':{
		'details':{
			'count':0
		},
		'Process':{
			'details':{
				'count':0
			},
			'Furnace':{
				'details':{
					'count':0
				}
			}
		}
	}
}

Basically I'd like to be able to parse a Tag/Folder path that looks like [default]Process or [default]Process/Furnace using an expression rather than a script.

Any pointers as to where to start?

Hmmm. Technically, that's a recursive operation, which I don't support. But I imagine you could hard-code a few levels with coalesce(). Start with my parsePath() function.

That was the route I originally took, but I can't seem to access the path of a document tag via dynamic data. I'll keep playing around though and see if I can make it happen.

Note: This is all happening in a UDT as well, not on the UI layer.

Hey @pturmel, I've got the latest as of writing installed (2.1.4) in 8.3.6, and pageVarMap isn't showing up in the functions list and it also isn't highlighting as if it's a recognised function, however it still returns a valid response:

image

Hmmm. Look for log entries for its registration getting rejected, and email me the backtrace. If it works, it registered. I'll have to check its doc methods.

2 Likes

Trying forEach for the first time. Using it inside a tag expression and seeing odd behavior.

Is there a reason that it requires jsonEncode wrapping it to get the correct output?

For example,

.stopCondNew = [0, 1, 8, ...]

forEach({[.]stopCondNew}, case(it(), 0, 'N/A', ... ,'UNK')) 

 -> causes "Bad Not Found" for the tag?

asList(forEach({[.]stopCondNew}, case(it(), 0, 'N/A', ... ,'UNK')))

 -> somewhat works, [["NA",...]]

jsonEncode(forEach({[.]stopCondNew}, case(it(), 0, 'N/A', ... ,'UNK'))) 

 -> works, returns ["NA",...]

These are all for type Document
Why are we not able to use others, say Integer Array?
The documentation for forEach says it returns PyList, but the conversion error says its ArrayList

Or with a simpler example:

forEach(asList(1, 2, 3), it()) 
-> tag type is document: refuses to return a value
-> tag type is int array: conversion error (says its ArrayList)
-> tag type is String: shows [1, 2, 3] as a string

Yeah, you are hitting Document tag issues I've encountered, with fixes in my next release. Some breaking changes, so I'm been toying with it some more. Will release soon.

2 Likes

Would that fix the conversion issue as well, or do we have to use the Document type?
Maybe I'm not understanding the return types correctly

And sounds good, will keep my eye out.
Thanks Phil!

Hi Phil,

I'm having an issue with the orderBy() expression function and different numeric types. I'm running Ignition 8.1.48 and Integration Toolkit 2.1.4.

The original setup was a script setting a globalVarMap() variable, and then an expression getting that value. Integers came out as either Integer or BigInteger depending on what the initial value was in the script.

orderBy() then throws an exception about not being able to cast between the two. It switches between a generic java.lang.ClassCastException: null and this one with a traceback:

java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.math.BigInteger (java.lang.Integer and java.math.BigInteger are in module java.base of loader 'bootstrap')
at java.base/java.math.BigInteger.compareTo(Unknown Source)
at java.base/java.util.Arrays.compare(Unknown Source)
at com.automation_pros.simaids.common.expressions.OrderBy$OrderableRow.compareTo(OrderBy.java:43)
at com.automation_pros.simaids.common.expressions.OrderBy$OrderableRow.compareTo(OrderBy.java:32)
at java.base/java.util.ComparableTimSort.countRunAndMakeAscending(Unknown Source)
at java.base/java.util.ComparableTimSort.sort(Unknown Source)
at java.base/java.util.Arrays.sort(Unknown Source)
at java.base/java.util.Arrays.sort(Unknown Source)
at java.base/java.util.ArrayList.sort(Unknown Source)
at com.automation_pros.simaids.common.expressions.OrderBy.postProcess(OrderBy.java:69)
at com.automation_pros.simaids.common.expressions.SimpleIterator.execute(SimpleIterator.java:223)
at com.inductiveautomation.ignition.common.expressions.FunctionExpression.execute(FunctionExpression.java:69)
at com.automation_pros.simaids.common.expressions.DebugMe.execute(DebugMe.java:47)
at com.inductiveautomation.ignition.common.expressions.FunctionExpression.execute(FunctionExpression.java:69)
at com.inductiveautomation.perspective.gateway.binding.PerspectiveExpression.execute(PerspectiveExpression.java:164)
at com.inductiveautomation.perspective.gateway.binding.PerspectiveExpression$EvaluationTask.runInternal(PerspectiveExpression.java:301)
at com.inductiveautomation.perspective.gateway.api.LoggingContext.mdc(LoggingContext.java:54)
at com.inductiveautomation.perspective.gateway.binding.PerspectiveExpression$EvaluationTask.run(PerspectiveExpression.java:280)
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)
at com.inductiveautomation.perspective.gateway.threading.BlockingWork$BlockingWorkRunnable.run(BlockingWork.java:58)
at java.base/java.lang.Thread.run(Unknown Source)

Casting the values to a integer with either int() in the script or toInt() in the expression works as an immediate fix.

Unfortunately, nothing I can really do about that. Keys for ordering must be comparable to each other, and java doesn't guarantee that for implementations of Number. That workaround is the right answer.

3 Likes

Automation Professionals is pleased to announce new Production Releases of this module with the following new utility functions:

  • system.tag.defineToolkitProp()
  • system.tag.removeToolkitProp()

For Ignition v8.3: v2.1.5.261812214

For Ignition v8.1: v2.1.5.261812141

The defineToolkitProp() function is intended to be called from a gateway startup event, once for each custom property the user wants to make available in the designer tag editor (with datatype predefined, optional default value, and other metadata). Unfortunately, the category must be a bundle key (for non-obvious reasons) and there is no runtime mechanism for adding bundle keys.
:frowning:

The removeToolkitProp() function is intended to be called by a designer (indirectly via @RunInGateway) during development of infrastructure that uses this feature.

The default category name is supplied by this module's main bundle.

An enhanced Spreadsheet Import Tool is coming available that takes advantage of this new feature.

3 Likes

Sounds interesting. What are some examples of other category bundles? (or, where would I find a list?)

I assume you mean that defineToolkitProp would add custom props into these tag prop categories?

LocalizedString.createRaw?