# Client scope - notify script

**URL:** <https://forum.inductiveautomation.com/t/client-scope-notify-script/21499>\
**Category:** Module Development\
**Created:** [December 19, 2018, 4:12pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499 "2018-12-19T16:12:26Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![mazeyrat](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mazeyrat/32/10955_2.png) [@mazeyrat](https://forum.inductiveautomation.com/u/mazeyrat)\
**Post date:** [December 19, 2018, 4:12pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/1 "2018-12-19T16:12:26Z")

</div>

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).

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 19, 2018, 4:21pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/2 "2018-12-19T16:21:37Z")

</div>

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](http://www.jython.org/javadoc/org/python/core/PyObject.html) when registering it.

---

<div class="post-metadata">

**Author:** ![mazeyrat](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mazeyrat/32/10955_2.png) [@mazeyrat](https://forum.inductiveautomation.com/u/mazeyrat)\
**Post date:** [December 19, 2018, 4:50pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/3 "2018-12-19T16:50:45Z")

</div>

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 ?

---

<div class="post-metadata">

**Author:** ![mazeyrat](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mazeyrat/32/10955_2.png) [@mazeyrat](https://forum.inductiveautomation.com/u/mazeyrat)\
**Post date:** [December 19, 2018, 9:16pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/4 "2018-12-19T21:16:48Z")

</div>

Example :

Provide a script function to register a callback PyObject :

```auto
    @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 :

```auto
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 :

```auto
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)

```

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 19, 2018, 9:52pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/5 "2018-12-19T21:52:12Z")

</div>

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

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

system.mylib.registerCallback(myCallback)

```

---

<div class="post-metadata">

**Author:** ![mazeyrat](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mazeyrat/32/10955_2.png) [@mazeyrat](https://forum.inductiveautomation.com/u/mazeyrat)\
**Post date:** [December 20, 2018, 10:54am UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/6 "2018-12-20T10:54:35Z")

</div>

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…

```auto
    @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()

---

<div class="post-metadata">

**Author:** ![mazeyrat](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mazeyrat/32/10955_2.png) [@mazeyrat](https://forum.inductiveautomation.com/u/mazeyrat)\
**Post date:** [December 20, 2018, 12:06pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/7 "2018-12-20T12:06:16Z")

</div>

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

```auto
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 :

```auto
this.callback. __call__ (payload);

```

```auto
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)

```

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 20, 2018, 12:41pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/8 "2018-12-20T12:41:43Z")

</div>

> [@mazeyrat](#):
>
> It works with : PyJavaType.wrapJavaObject(val.getValue()) instead of (PyObject) val.getValue()

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.

---

<div class="post-metadata">

**Author:** ![mazeyrat](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mazeyrat/32/10955_2.png) [@mazeyrat](https://forum.inductiveautomation.com/u/mazeyrat)\
**Post date:** [December 20, 2018, 2:18pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/9 "2018-12-20T14:18:51Z")

</div>

Yes it works for **system** function but not if I import some **shared** function. 😞

---

<div class="post-metadata">

**Author:** ![mazeyrat](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mazeyrat/32/10955_2.png) [@mazeyrat](https://forum.inductiveautomation.com/u/mazeyrat)\
**Post date:** [December 20, 2018, 4:31pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/10 "2018-12-20T16:31:22Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 20, 2018, 6:18pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/11 "2018-12-20T18:18:24Z")

</div>

> [@mazeyrat](#):
>
> Yes it works for system function but not if I import some shared function.

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

> [@mazeyrat](#):
>
> Any Idea why print "message" in the calback function registered are not visible in the designer console ?

Background threads do not reliably print. Use a logger.

---

<div class="post-metadata">

**Author:** ![mazeyrat](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mazeyrat/32/10955_2.png) [@mazeyrat](https://forum.inductiveautomation.com/u/mazeyrat)\
**Post date:** [December 20, 2018, 6:25pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/12 "2018-12-20T18:25:41Z")

</div>

Whaooo…😀 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

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 20, 2018, 6:36pm UTC](https://forum.inductiveautomation.com/t/client-scope-notify-script/21499/13 "2018-12-20T18:36:40Z")

</div>

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.
