Client scope - notify script

I’m looking for a way to notify Ignition Script from the client scope side of a module.
Is it possible to create a kind of client script handler that the module could trigger ? ou to register a python callback function the module could trigger ?

(I can’t use client message handler because these kind of notification are used only for message from gateway scope to client scope).

Yeah, you can do this. Have your client scripts call a “register” function to supply a python callable for your module to remember, and have your gateway send push notifications. The listener in your module would look up the registered callable to pass the message, or discard if not registered. Check the isCallable() of the PyObject when registering it.

1 Like

Thanks @pturmel, I will try this solution,
a register client scoped script function with a PyObject Class parameter.
The module then trigger the invoke function with parameters to notify the script.
I will instanciate the PyObject in a windows always visible or as global variable in a client script, but
do you know how to unregister the PyObject, for example if client script are updated ?

Example :

Provide a script function to register a callback PyObject :

    @ScriptFunction(docBundlePrefix = Constantes.BUNDLE_PREFIX)
    @KeywordArgs(names = {"callback"},types = {PyObject.class})
    public boolean registerCallback(PyObject[] pyArgs, String[] keywords) {
        try {
            PyArgumentMap args = PyArgumentMap.interpretPyArgs(pyArgs, keywords, ClientScriptModuleDirect.class, "registerCallback");
            if (args.containsKey("callback")) {
                PyObject object = (PyObject) args.get("callback");
                if (object.isCallable()){
                    this.callback = object;
                    return true;
                } else {
                    this.callback = null;
                    logger.error("registerCallback() - callback PyObject is not Callable");
                }
            }else{
                logger.info("registerCallback() - argument callback is mandatory");
            }
            return false;
        } catch (Exception e) {
            logger.error("Exception : ", e);
            return false;
        }
    }

trigger the callback in the client module scope to notify ignition client script :

List<String> message =new ArrayList<String>();
message.add("val1");
message.add("val2");
PyObject payload = new PyList(message);
callback.__call__(payload);

Create the callback object in Ignition script :

import system
class Notif():
	def __init__(self, x=0):
		self.x = x

	def __call__(self,payload):
		print "called"
		print payload


test = Notif()
system.mylib.registerCallback(callback=test)
1 Like

Your callback can be a simple function, as they are automatically callable:

def myCallback(payload):
    print "called"
    print payload

system.mylib.registerCallback(myCallback)
2 Likes

Thanks @pturmel for these clarification.
I try to pass a dictionnary parameter to the callback function with the following code,
but it generate an exception on PyObject) val.getValue())
I’ve certainly miss somethng…Do you know how to pass a dictionnary to the callback function ?
callback.call(message); is not possible…

    @Override
    public void notify(Map<String,Object> message) {
        try {
            if (callback != null) {
                  Map<PyObject, PyObject> dico = new HashMap<PyObject, PyObject>();
                if (message != null) {
                    for (Map.Entry<String, Object> val : message.entrySet()) {
                        dico.put(new PyString(val.getKey()), (PyObject) val.getValue());
                    }
                }
                PyDictionary payload = new PyDictionary(dico);
                callback.__call__(payload);
            } else {
                logger.error("notify sip message but no callback registered. use registerCallback");
            }
        } catch (Exception e) {
            logger.error("Exception : ", e);
        }
    }

It works with : PyJavaType.wrapJavaObject(val.getValue()) instead of (PyObject) val.getValue()

@pturmel, when I had some traits in the callback function (registered on an action performed button script)

def myCallback(payload):
    import system
	print "call me"
	print "<====="+str(payload)
	system.myLib.accept(1)

Print are not visible in the designer console but I have them in the java logger…
and an exception is generated when the callback is triggered.

Is there anythink with thread context to provide in the module ???
additionnal parameter to :

this.callback.__call__(payload);
ImportError: No module named system

	at org.python.core.Py.ImportError(Py.java:304)
	at org.python.core.imp.import_first(imp.java:755)
	at org.python.core.imp.import_module_level(imp.java:837)
	at org.python.core.imp.importName(imp.java:917)
	at org.python.core.ImportFunction.__call__(__builtin__.java:1220)
	at org.python.core.PyObject.__call__(PyObject.java:357)
	at org.python.core.__builtin__.__import__(__builtin__.java:1173)
	at org.python.core.imp.importOne(imp.java:936)
	at org.python.pycode._pyx61.test$5(<event:actionPerformed>:46)
	at org.python.pycode._pyx61.call_function(<event:actionPerformed>)
	at org.python.core.PyTableCode.call(PyTableCode.java:165)
	at org.python.core.PyBaseCode.call(PyBaseCode.java:134)
	at org.python.core.PyFunction.__call__(PyFunction.java:317)
	at org.python.core.PyFunction.__call__(PyFunction.java:312)
	at com.bouyguesenergiesservices.ignition.client.sipclient.ClientScriptModuleDirect.notify(ClientScriptModuleDirect.java:78)

Yeah, you can't cast to PyObject. You have to convert to PyObject. The correct method for this is Py.java2py(), which defers to registered wrappers where appropriate.[quote="mazeyrat, post:7, topic:21499"]
when I had some traits in the callback function
[/quote]
Move your "import system" statement to above the "def", so that that name is in the function's closure. Or define your callback functions in script modules where modern scoping applies.

Yes it works for system function but not if I import some shared function. :disappointed:

Any Idea why print "message" in the calback function registered are not visible in the designer console ?

Try just import shared above the function. Not the whole shared function name.

Background threads do not reliably print. Use a logger.

1 Like

Whaooo…:grinning: good shot again. many thanks !

with only import shared, print it in the console and my shared.logger function too !
Strange it’s different from import shared.mylib.myfunc

It is an artifact of backward compatibility to some old versions of Ignition. Only on some event functions. Functions and classes defined within those events have a shared globals namespace and an empty local namespace. It is recommended that all real functions and classes that you will be passing around as objects be defined in a shared.* or project.* script module, where modern per-script-module scoping rules apply. Such event routines basically just call the script module function and pass any event object to it. That avoids this whole mess.