Use sendRequest to get project script variable from console

Ignition v8.1.39
Perspective

The TL;DR
Is it possible to access the same variable instance as it is defined in an inheritable project top level script from the parent and child designer script console?

I have a dictionary variable plants defined in the top level of a project script. It is filled with instances of my own class called Plant unconditionally, also in the top level of the script. The project scripts are in an inheritable project, that is inherited by one project.

Access to the plants variable is wanted in the script console while in either project designer for testing. If I call projectScript.getPlant('plantName'), it does not return the same instance. As I understand it, I cannot access other scopes unless I send a message.
I created a gateway message handler in the parent project named getPlant.
However, I am unable to get it to work in the parent's or child's script console.
When I try sendRequest from either parent or child's script console, using project name childProj the designer disconnects briefly from the gateway, and a popup displays saying 'Waiting For Gateway...' with exception No module named Plant.

# from perspective binding in view on parentProj
# i set the message handler to return projectScript.getPlant
payload = {'plantName': 'Plant1'}
plant = system.util.sendRequest('childProj', 'getPlant', payload = payload)
>>>
<function wrapper at 0x43>

# from parent console
print projectScript.getPlant
>>>
<function wrapper at 0x3>

# from child console
print projectScript.getPlant
>>>
<function wrapper at 0x4>
def handleMessage(payload):
# gateway message handler defined in parent project
	plantName = payload.get('plantName')
	if plantName is None:
		plantName = 'Default'

	return projectScript.getPlant(plantName)
# defined in parent project script
import threading
lock = threading.Lock()

plants = {}
plantConfigs = {
    'Plant1': {
    'tagProvider': 'p1TagProvider'
    # many other configuration parameters
    }
    'Plant2':{
    'tagProvider': 'p2TagProvider'
    # many other configuration parameters
    }
}

with lock:
    for plantName, plantConfig in plantConfigs.items():
        plant = projectScript.Plant.Plant(name = plantName, config = plantConfig)
      
        # calls system.tag.browse
        machines = projectScript.Tags.getMachineTags(plant.getTagProvider())
        
        for machine in machines:
            machineInst = projectScript.Machine.Machine(name = machine.get('name'), tagProvider = plant.getTagProvider())

        plant.addMachine(machineInst)
        plants.setdefault(plantName, plant)
        

def getPlant(plantName):
	global plants
	return plants.get(plantName)
# called from parent/child script console using parentProj name
# fails because inheritable projects cannot execute handler scripts
payload = {'plantName': 'Default'}
plant = system.util.sendRequest('parentProj', 'getPlant', payload = payload)
print plant

[SwingWorker-pool-1-thread-10] ERROR designer.scripting.SystemUtilities -- Error invoking sendRequest from client.
com.inductiveautomation.ignition.client.gateway_interface.GatewayException: Project 'parentProj' is inheritable and cannot execute message handler scripts.
# called from parent/child script console
# designer disconnects briefly and gets ImportError: No module named Plant.
payload = {'plantName': 'Default'}
plant = system.util.sendRequest('childProj', 'getPlant', payload = payload)
print plant

10:26:59.668 [DesignerExecEngine-3] WARN com.inductiveautomation.ignition.client.gateway_interface.GatewayConnectionManager -- Connection to Gateway lost, due to exception.
com.inductiveautomation.ignition.client.gateway_interface.GatewayException: ImportError: No module named Plant
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.newGatewayException(GatewayInterface.java:360)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.getResponse(GatewayInterface.java:556)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.getResponse(GatewayInterface.java:366)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.ping(GatewayInterface.java:1024)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.ping(GatewayInterface.java:1009)
	at com.inductiveautomation.ignition.client.tags.impl.GatewayTagInterface.runPoll(GatewayTagInterface.java:128)
	at com.inductiveautomation.ignition.common.execution.impl.BasicExecutionEngine$TrackedTask.run(BasicExecutionEngine.java:593)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
	at java.base/java.util.concurrent.FutureTask.runAndReset(Unknown Source)
	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.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: org.python.core.PyException: ImportError: No module named Plant
	at org.python.core.Py.ImportError(Py.java:327)
	at org.python.core.imp.import_first(imp.java:1232)
	at org.python.core.imp.import_module_level(imp.java:1363)
	at org.python.core.imp.importName(imp.java:1520)
	at org.python.core.PyType$TypeResolver.readResolve(PyType.java:2674)
	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 java.base/java.io.ObjectStreamClass.invokeReadResolve(Unknown Source)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
	at java.base/java.io.ObjectInputStream.readObject0(Unknown Source)
	at java.base/java.io.ObjectInputStream$FieldValues.<init>(Unknown Source)
	at java.base/java.io.ObjectInputStream.readSerialData(Unknown Source)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
	at java.base/java.io.ObjectInputStream.readObject0(Unknown Source)
	at java.base/java.io.ObjectInputStream$FieldValues.<init>(Unknown Source)
	at java.base/java.io.ObjectInputStream.readSerialData(Unknown Source)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
	at java.base/java.io.ObjectInputStream.readObject0(Unknown Source)
	at java.base/java.io.ObjectInputStream.readObject(Unknown Source)
	at java.base/java.io.ObjectInputStream.readObject(Unknown Source)
	at com.inductiveautomation.ignition.common.Base64.decodeToObjectFragile(Base64.java:985)
	at com.inductiveautomation.ignition.common.Base64.decodeToObjectFragile(Base64.java:959)
	at com.inductiveautomation.ignition.client.gateway_interface.ResponseParser.endElement(ResponseParser.java:165)
	at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.getResponse(GatewayInterface.java:498)
	... 11 common frames omitted
10:26:59.685 [DesignerExecEngine-4] INFO tags.subscriptions -- Changing connected quality to 'Bad_Stale'
10:26:59.685 [DesignerExecEngine-3] INFO Scripting.ScriptManager.childProj -- Pausing scripts...
10:26:59.685 [DesignerExecEngine-3] INFO com.inductiveautomation.ignition.client.gateway_interface.GatewayConnectionManager -- Starting reconnect thread.
10:26:59.785 [GatewayConnection-1] INFO tags.subscriptions -- Changing connected quality to 'Good'
10:26:59.786 [GatewayConnection-1] INFO Scripting.ScriptManager.childProj -- Resuming scripts...
10:26:59.858 [GatewayConnection-1] INFO com.inductiveautomation.ignition.client.gateway_interface.GatewayConnectionManager -- Stopping reconnect thread.
10:27:59.561 [SwingWorker-pool-1-thread-9] ERROR designer.scripting.SystemUtilities -- Error invoking sendRequest from client.
com.inductiveautomation.ignition.client.gateway_interface.GatewayException: Results for asynchronous rpc call were not received within the specified amount of time [60000 ms] for task '16043852-3e0d-4d2b-aeef-ef65212a3138'
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.newGatewayException(GatewayInterface.java:360)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.sendMessage(GatewayInterface.java:330)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.sendMessage(GatewayInterface.java:287)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.invoke(GatewayInterface.java:967)
	at com.inductiveautomation.ignition.client.script.DesignerSystemUtilities$SendRequestManager.invokeRequest(DesignerSystemUtilities.java:388)
	at com.inductiveautomation.ignition.client.script.DesignerSystemUtilities.sendRequestInternal(DesignerSystemUtilities.java:217)
	at com.inductiveautomation.ignition.common.script.builtin.SystemUtilities.sendRequestInternal(SystemUtilities.java:907)
	at com.inductiveautomation.ignition.common.script.builtin.SystemUtilities.sendRequest(SystemUtilities.java:828)
	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:190)
	at com.inductiveautomation.ignition.common.script.ScriptManager$ReflectedInstanceFunction.__call__(ScriptManager.java:553)
	at org.python.core.PyObject.__call__(PyObject.java:400)
	at org.python.pycode._pyx172.f$0(<input>:10)
	at org.python.pycode._pyx172.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:628)
	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:616)
	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.util.concurrent.TimeoutException: Results for asynchronous rpc call were not received within the specified amount of time [60000 ms] for task '16043852-3e0d-4d2b-aeef-ef65212a3138'
	at com.inductiveautomation.ignition.client.util.gui.progress.ClientProgressManager$RunningTask.waitForResult(ClientProgressManager.java:453)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.sendMessage(GatewayInterface.java:314)
	... 29 common frames omitted

You cannot send arbitrary objects through Remote Procedure Calls. Certainly not any jython class instances. The designer (and therefore its script console) is a completely different JVM process from the gateway, even when running on the same machine.

3 Likes

Thanks, Phil.
So testing in script console will have to be its own instance.

Just for kicks, I changed the message handler and tested it within a perspective binding on a view in the parent. It does return the class instance.

def handleMessage(payload):
	plantName = payload.get('plantName')
	if plantName is None:
		plantName = 'CGV'
    # get value directly from global variable
	return projectScript.plants.get(plantName)
def transform(self, value, quality, timestamp):
	payload = {'plantName': 'Plant1'}
	plant = system.util.sendRequest('childProj', 'getPlant', payload = payload)
	return plant.getMachine('Machine1').getLastEvent()

Any testing of scripts intended to be used with Perspective is ill fated. The script console executes its scripts in a modified 'Vision Client Scope' which leads to all kinds of unexpected results.

For testing perspective scripts of any kind you need a way to delegate that execution scope to the gateway.

Perhaps system.util.runInGateway?

Sure, if you have Phil's module, that is the task that decorator was created for (delegating a function to run in Gateway Scope).

However, I would caution you to read the documentation, there are caveats to its use.

My runInGateway decorator still does RPC from the designer script console, so is no help for this case.

Test with an event action script on a Perspective button in a test view.

Thanks for the guidance, guys.