예제 #1
0
def test_emit__7():
    """
    False event can be emitted.
    """
    emitter = Emitter()
    l = []
    emitter.on(False, lambda: l.append(1))
    emitter.emit(False)
    assert 1 in l
예제 #2
0
def test_once__3():
    """
    Event should be cleaned if no more listeners.
    """
    emitter = Emitter()
    emitter.once("event", callable)
    emitter.emit("event")

    assert emitter.events() == set()
예제 #3
0
def test_emit__7():
    """
    False event can be emitted.
    """
    emitter = Emitter()
    l = []
    emitter.on(False, lambda: l.append(1))
    emitter.emit(False)
    assert 1 in l
예제 #4
0
def test_emit__8():
    """
    True event can be emitted.
    """
    emitter = Emitter()
    l = []
    emitter.on(True, lambda: l.append(1))
    emitter.emit(True)
    assert 1 in l
예제 #5
0
def test_emit__8():
    """
    True event can be emitted.
    """
    emitter = Emitter()
    l = []
    emitter.on(True, lambda: l.append(1))
    emitter.emit(True)
    assert 1 in l
예제 #6
0
def test_once__3():
    """
    Event should be cleaned if no more listeners.
    """
    emitter = Emitter()
    emitter.once("event", callable)
    emitter.emit("event")

    assert emitter.events() == set()
예제 #7
0
def test_emit__3():
    """
    Only the listeners of the specified event should be triggered.
    """
    emitter = Emitter()
    l = []
    emitter.on("event1", lambda: l.append(1))
    emitter.on("event2", lambda: l.append(2))
    emitter.emit("event1")
    assert l == [1]
예제 #8
0
def test_emit__3():
    """
    Only the listeners of the specified event should be triggered.
    """
    emitter = Emitter()
    l = []
    emitter.on("event1", lambda: l.append(1))
    emitter.on("event2", lambda: l.append(2))
    emitter.emit("event1")
    assert l == [1]
예제 #9
0
def test_once__2():
    """
    Listener should be removed after call.
    """
    emitter = Emitter()
    emitter.once("event", callable)
    emitter.on("event", bool)
    emitter.emit("event")

    assert callable not in emitter.listeners("event")
    assert bool in emitter.listeners("event")
예제 #10
0
def test_once__4():
    """
    Allow updating an event from once() to on().
    """
    emitter = Emitter()
    emitter.on("event", callable)
    emitter.once("event", callable)

    emitter.emit("event")

    assert callable not in emitter.listeners("event")
예제 #11
0
def test_once__4():
    """
    Allow updating an event from once() to on().
    """
    emitter = Emitter()
    emitter.on("event", callable)
    emitter.once("event", callable)

    emitter.emit("event")

    assert callable not in emitter.listeners("event")
예제 #12
0
def test_emit__2():
    """
    Listeners are triggered in order of insertion.
    """
    emitter = Emitter()
    l = []
    emitter.on("event", lambda: l.append(1))
    emitter.on("event", lambda: l.append(2))
    emitter.on("event", lambda: l.append(3))
    emitter.emit("event")
    assert l == [1, 2, 3]
예제 #13
0
def test_emit__2():
    """
    Listeners are triggered in order of insertion.
    """
    emitter = Emitter()
    l = []
    emitter.on("event", lambda: l.append(1))
    emitter.on("event", lambda: l.append(2))
    emitter.on("event", lambda: l.append(3))
    emitter.emit("event")
    assert l == [1, 2, 3]
예제 #14
0
def test_error__1(spy):
    """
    ERROR event is emitted when some listener raises exception.
    """
    emitter = Emitter()
    emitter.on("event", raise_error)
    emitter.on(Emitter.ERROR, spy)

    emitter.emit("event")

    assert spy.called(1)
예제 #15
0
def test_once__2():
    """
    Listener should be removed after call.
    """
    emitter = Emitter()
    emitter.once("event", callable)
    emitter.on("event", bool)
    emitter.emit("event")

    assert callable not in emitter.listeners("event")
    assert bool in emitter.listeners("event")
예제 #16
0
def test_emit__1():
    """
    All the listeners of an event must be triggered.
    """
    emitter = Emitter()
    l = []
    emitter.on("event", lambda: l.append(1))
    emitter.on("event", lambda: l.append(1))
    emitter.on("event", lambda: l.append(1))
    emitter.emit("event")
    assert len(l) == 3
예제 #17
0
def test_emit__1():
    """
    All the listeners of an event must be triggered.
    """
    emitter = Emitter()
    l = []
    emitter.on("event", lambda: l.append(1))
    emitter.on("event", lambda: l.append(1))
    emitter.on("event", lambda: l.append(1))
    emitter.emit("event")
    assert len(l) == 3
예제 #18
0
def test_on__11():
    """
    One-shot listeners can be called only once.
    """
    emitter = Emitter()
    l = []
    emitter.on("event", lambda: l.append(1), True)
    emitter.emit("event")
    emitter.emit("event")
    emitter.emit("event")

    assert len(l) == 1
예제 #19
0
def test_on__11():
    """
    One-shot listeners can be called only once.
    """
    emitter = Emitter()
    l = []
    emitter.on("event", lambda: l.append(1), True)
    emitter.emit("event")
    emitter.emit("event")
    emitter.emit("event")

    assert len(l) == 1
예제 #20
0
def test_once__1():
    """
    Listener can be called only once.
    """
    emitter = Emitter()
    l = []
    emitter.once("event", lambda: l.append(1))
    emitter.emit("event")
    emitter.emit("event")
    emitter.emit("event")

    assert len(l) == 1
예제 #21
0
def test_once__1():
    """
    Listener can be called only once.
    """
    emitter = Emitter()
    l = []
    emitter.once("event", lambda: l.append(1))
    emitter.emit("event")
    emitter.emit("event")
    emitter.emit("event")

    assert len(l) == 1
예제 #22
0
class TestEmitter(TestCase):
    def setUp(self):
        self.emitter = Emitter()

    def test_call_listeners(self):
        called = [False]

        def cb():
            called[0] = True

        self.emitter.on('hello.*', cb)
        self.emitter.emit('hello.foo')
        assert called[0]
        self.emitter.remove('hello.*', cb)
        assert self.emitter.listeners('hello') == []

    def test_pass_data_to_listeners(self):
        def cb(*args):
            assert args == ('a', 'b')

        self.emitter.on('data', cb)
        self.emitter.emit('data', 'a', 'b')

    def test_call_listeners_once(self):
        called = [False]

        def cb():
            called[0] = not called[0]

        self.emitter.on('once', cb, True)
        self.emitter.emit('*')
        self.emitter.emit('on*')
        assert called[0]

    def test_remove_listeners(self):
        self.emitter.on('foo', lambda x: x)
        self.emitter.on('bar', lambda x: x)

        self.emitter.remove('bar')
        self.emitter.remove('*')
        assert self.emitter.listeners('foo') == []
        assert self.emitter.listeners('bar') == []

    def test_emit_unknown_events(self):
        self.emitter.emit('quux')
        self.emitter.remove('wut')

    def test_provide_listeners(self):
        def cb(): pass

        self.emitter.on('quux', cb)
        assert self.emitter.listeners('*') == [cb]
예제 #23
0
def test_error__4(spy):
    """
    One time listener is removed even if it raises exception.
    """
    emitter = Emitter()

    emitter.once("event", raise_error)

    assert len(emitter.listeners("event")) == 1

    emitter.emit("event")

    assert len(emitter.listeners("event")) == 0
예제 #24
0
def test_error__3(spy):
    """
    If error handler raises exception, it is re-raised, but not catched
    by the emitter this time (no ERROR event this time).
    """
    emitter = Emitter()

    emitter.on("event", spy.throw)
    emitter.on(Emitter.ERROR, spy.throw)

    with pytest.raises(Exception):
        emitter.emit("event")

    assert spy.called(2)
예제 #25
0
def test_once__5():
    """
    One time listeners should be removed even if an error happens.
    """
    emitter = Emitter()

    def listener(*args, **kwargs):
        raise Exception()

    emitter.once("event", listener)

    emitter.emit("event")

    assert listener not in emitter.listeners("event")
예제 #26
0
def test_once__5():
    """
    One time listeners should be removed even if an error happens.
    """
    emitter = Emitter()

    def listener(*args, **kwargs):
        raise Exception()

    emitter.once("event", listener)

    emitter.emit("event")

    assert listener not in emitter.listeners("event")
예제 #27
0
def test_error__5(spy):
    """
    One time ERROR listener is removed even if it raises exception.
    """
    emitter = Emitter()

    emitter.once(Emitter.ERROR, spy.throw)
    emitter.on("event", raise_error)

    assert len(emitter.listeners(Emitter.ERROR)) == 1

    with pytest.raises(Exception):
        emitter.emit("event")

    assert len(emitter.listeners(Emitter.ERROR)) == 0
예제 #28
0
def test_emit__6():
    """
    Should pass *args and **kwargs to the listeners.
    """
    emitter = Emitter()
    params = []

    def listener(param1, param2, unused=None, param3=None):
        params.append(param1)
        params.append(param2)
        params.append(unused)
        params.append(param3)

    emitter.on("event", listener)
    emitter.emit("event", 10, 20, param3="hello")
    assert params == [10, 20, None, "hello"]
예제 #29
0
def test_emit__4():
    """
    Returns False when emitting a non-existent event.
    """
    emitter = Emitter()
    result = emitter.emit("event")
    assert result is False
예제 #30
0
def test_emit__9():
    """
    Emitting None event returns False.
    """
    emitter = Emitter()

    assert emitter.emit(None) is False
예제 #31
0
def test_emit__9():
    """
    Emitting None event returns False.
    """
    emitter = Emitter()

    assert emitter.emit(None) is False
예제 #32
0
def test_emit__6():
    """
    Should pass *args and **kwargs to the listeners.
    """
    emitter = Emitter()
    params = []

    def listener(param1, param2, unused=None, param3=None):
        params.append(param1)
        params.append(param2)
        params.append(unused)
        params.append(param3)

    emitter.on("event", listener)
    emitter.emit("event", 10, 20, param3="hello")
    assert params == [10, 20, None, "hello"]
예제 #33
0
def test_emit__4():
    """
    Returns False when emitting a non-existent event.
    """
    emitter = Emitter()
    result = emitter.emit("event")
    assert result is False
예제 #34
0
def test_emit__5():
    """
    Returns True when emitting an event.
    """
    emitter = Emitter()
    emitter.on("event", callable)
    result = emitter.emit("event")
    assert result is True
예제 #35
0
def test_emit__5():
    """
    Returns True when emitting an event.
    """
    emitter = Emitter()
    emitter.on("event", callable)
    result = emitter.emit("event")
    assert result is True
예제 #36
0
def test_error__2(spy):
    """
    ERROR event handlers get error data (sys.exc_info), *args and **kwargs.
    """
    emitter = Emitter()

    emitter.on("event", raise_error)
    emitter.on(Emitter.ERROR, spy)

    emitter.emit("event", 10, b=20)

    assert spy.called(1)

    # first arg passed to the error handler is a tuple with 3 elements
    # see sys.exc_info()
    assert isinstance(spy.args[0][1], Exception)

    assert spy.args[1] == 10
    assert spy.kwargs["b"] == 20
예제 #37
0
 def addEmitter(self):
     emit = Emitter(self.xPos, self.yPos, None, 3000)
     self.emitters.append(emit)
     emit.emit(50)
예제 #38
0
class ServiceEndpoint():
    service: Optional[str] = None
    socket: socketio.client = None
    emitter: Optional[Emitter] = None

    def __init__(self, serviceName: str):
        self.emitter = Emitter()
        print(f"CONSTRUCTOR OF SE {self.emitter}")
        self.service = serviceName
        self.socket = socketio.Client(logger=True)
        self.socket.connect(f'http://{HOST}:{PORT}', {'socketio_path': '/'},
                            'websocket')

        self.socket.on(EVENTS_MAP.CONNECT.value, self.subscribe_to_service)
        self.socket.on(EVENTS_MAP.SERVICE_RESPONSE.value,
                       self.service_response)

    def close_connection(self) -> None:
        self.socket.disconnect()

    def get_socket(self) -> socketio.client:
        return self.socket

    def subscribe_to_service(self) -> None:
        self.socket.emit(EVENTS_MAP.SERVICE_ATTACH.value, self.service)
        self.socket.on(EVENTS_MAP.SERVICE_ATTACH.value, self.service_attach)
        print("Subscribe to service")

    def service_response(self, value: Any) -> Any:
        print(f"value incoming {value}")
        return value

    def get_properties(self, service: str) -> None:
        print(f"service = {service}")

        self.socket.emit(EVENTS_MAP.SERVICE_REQUEST.value, {
            'id': getId(),
            'method': '<get>',
            'params': {},
            'service': service
        })

    def service_request(self, value: str) -> None:
        print(f"Hello world {value}")

    def service_event(self, value: Any) -> None:
        print(f"Service event handler {value}")

    def service_attach(self, service: str) -> None:
        self.socket.on(EVENTS_MAP.SERVICE_REQUEST.value, self.service_request)
        self.socket.on(EVENTS_MAP.SERVICE_EVENT.value, self.service_event)

        print('service attached ', service)

        if service == self.service:
            self.socket.emit(EVENTS_MAP.SERVICE_SUBSCRIBE.value, self.service)
            self.emitter.emit("ep_attached", {'name': self.service})

        # if service == SERVICES_MAP.PRINTER_SERVICE.value:
        #     self.socket.emit(
        #         EVENTS_MAP.SERVICE_SUBSCRIBE.value,
        #         self.service
        #     )
        #     self.emitter.emit("ep_attached", True)

        # elif service == SERVICES_MAP.ORDER_SERVICE.value:
        #     self.socket.emit(
        #         EVENTS_MAP.SERVICE_SUBSCRIBE.value,
        #         SERVICES_MAP.ORDER_SERVICE.value
        #     )

    def set_printer_state(self, data: dict) -> None:
        print(f"Emit event from set_printer_state {data}")
        # self.set_printer_state({'message': 'hello world'})
        self.socket.emit(
            EVENTS_MAP.SERVICE_EVENT.value, {
                'service': SERVICES_MAP.PRINTER_SERVICE.value,
                'id': None,
                'params': [{
                    'printerErrorState': data
                }, []],
                'name': 'PropertiesChanged'
            })
예제 #39
0
class EpicsMonitor(QtCore.QObject):
    """
    This class caches EPICS PV data and calls user registered callbacks
    when PVs change.

    It also allows "local" (i.e., non-EPICS) variables to be defined.
    The usage of these local variables follows the same pattern as the
    EPICS variables.  That is, clients can register callbacks for EPICS
    and/or non-EPICS variables using the same pattern.
    """
    SIGNAL = QtCore.pyqtSignal(unicode)

    def __init__(self, *args, **kwargs):

        super(EpicsMonitor, self).__init__(*args, **kwargs)

        self.local_dict = {}
        self.pv_data = {}
        self.pv_dict = {}
        self.pv_connected = {}
        self.callback_dict = {}
        self.validator_dict = {}
        self.emitter = Emitter()

        self.SIGNAL.connect(self.signal_handler)

    def epics_version(self):
        import epics
        from epics.ca import find_libca

        dllname = find_libca()
        # print "DLL NAME", dllname
        return epics.__version__, dllname

    def emit(self, signal, value):
        self.emitter.emit(signal, value)

    def signal_handler(self, name):

        name = str(name)
        callbacks = self.callback_dict.get(name, [])

        if len(callbacks) > 5:
            print "MONITOR: Warning: callbacks for %s: %d" % \
                  (name, len(callbacks))

        for func in callbacks:
            func(name)
            #try:
            #    func(name)

            #except Exception, e:
            #    print "MONITOR: Exception: %s calling callback for %s" % \
            #          (str(e), name)
            #    print "  CALLBACK: %s" % repr(func)

    def disconnect_callback(self, name, func):
        callbacks = self.callback_dict.get(name, [])
        self.callback_dict[name] = [f for f in callbacks if f != func]

    def get_callback_list(self):
        """
        This method returns a dict of registered callbacks.
        It was added as a debugging tool
        """
        result = {}

        for key, value in self.pv_dict.iteritems():
            callbacks = self.callback_dict.get(key, [])
            if len(callbacks):
                result[key] = callbacks

        for key, value in self.local_dict.iteritems():
            callbacks = self.callback_dict.get(key, [])
            if len(callbacks):
                result[key] = callbacks

        return result

    def print_unconnected(self):
        """
        Prints unconnected EPICs PVs.  This is done for debugging as
        all EPICs PVs should be connected.
        """
        for key, value in self.pv_dict.iteritems():
            result = value.wait_for_connection(timeout=0)
            if result is False:
                print "----------------------------------------"
                print "NOT CONNECTED '%s': %s" % (key, value)

    def get_pv_list(self):
        """
        Returns a list of all PVs
        """
        return [name for name in self.pv_dict.iterkeys()]

    def is_monitored(self, pv_name):
        local_list = self.get_local_list()
        if pv_name in local_list:
            return True

        pv_list = self.get_pv_list()
        if pv_name in pv_list:
            return True

        return False

    def is_local(self, pv_name):

        local_list = self.get_local_list()
        if pv_name in local_list:
            return True

        return False

    def get_local_list(self):
        """
        Returns a list of all local vars
        """
        return [name for name in self.local_dict.iterkeys()]

    def print_list(self):

        for key, value in self.pv_dict.iteritems():
            print "----------------------------------------"
            print "MONITOR '%s': %s" % (key, value)
            print "  VALUE: %s" % repr(self.get_value(key))

            callbacks = self.callback_dict.get(key, [])
            for callback in callbacks:
                print "  CALLBACK: %s" % callback

        for key, value in self.local_dict.iteritems():
            print "MONITOR (LOCAL) '%s': %s" % (key, value)
            callbacks = self.callback_dict.get(key, [])
            for callback in callbacks:
                print "  CALLBACK: %s" % callback

        self.print_unconnected()

    def stop(self):
        print "MONITOR: stop() called"
        self.emitter.stop()

    def start(self, pv_name=None):
        """
        Emit a signal for each local variable of EPICS PV.
        this will call registered callbacks and allow clients
        to initialize themselves with monitored variable values.
        """
        for name in self.local_dict.iterkeys():
            if pv_name is None or pv_name == name:
                self.emit(self.SIGNAL, name)

        # Connect to actual EPICs PVs
        for name in self.pv_dict.iterkeys():
            if pv_name is None or pv_name == name:
                pv = self.pv_dict.get(name)
                if pv is None:
                    # Just creating PV object but not accessing any of its
                    # properties will not cause any network traffic
                    # (i.e. connection (attempt) to the pv).
                    pv = PV(name, connection_callback=self.connection_callback)
                    self.pv_dict[name] = pv

                    # print "ADDING CALLBACK FOR PV: %s" % name
                    pv.add_callback(self.pv_callback, index=0)

                #else:
                self.emit(self.SIGNAL, name)

    def reset(self, name):
        """
        Sometimes on startup a PV init is missed.  I have no idea
        why this happens (it is very rare).  Perhaps there is a race
        condition that causes a signal to be missed.  At any rate,
        this method call pv.info (the idea being that the PV will be
        queried across the network if necessary), then the callback
        is triggered.  This should signal the client to (re)read the PV
        """
        print "MONITOR: reset(%s) called" % name
        pv = self.pv_dict.get(name)
        if not name:
            print "MONITOR: cannot find PV '%s' for reset" % name
            return

        print pv.info
        pv.run_callback(0)

    def call_callbacks(self, name):

        # print "monitor.call_callbacks(): emitting SIGNAL(%s)" % name
        self.emit(self.SIGNAL, name)
        return True

    def put(self, name, value):

        name = unicode(name)

        # print "MONITOR: %s PUT %s" % (name, repr(value))

        validator = self.validator_dict.get(name)
        if validator:
            value = validator(name, value)

        if self.local_dict.has_key(name):
            return self.update(name, value, validated=True)

        pv = PV(name)
        pv.put(value)

    def update(self, name, value, validated=False):

        name = unicode(name)
        if not validated:
            validator = self.validator_dict.get(name)
            if validator:
                value = validator(name, value)

        # print "monitor %s %s " % (name, repr(value))
        if self.local_dict.has_key(name):
            if self.local_dict[name] == value:
                # print "monitor: no need to update %s %s" % (name, repr(value))
                return False
        self.local_dict[name] = value
        self.pv_connected[name] = True

        # print "monitor.update(): emitting SIGNAL(%s) (value: %s)" % (name, repr(value))
        self.emit(self.SIGNAL, name)

        return True

    def add(self, pvs, var=EPICS_PV_MARKER):
        """
        Add variable to list of monitored variables
        """
        local_flag = False

        if not isinstance(var, EpicsPVMarker):
            local_flag = True

        if not local_flag:
            if not isinstance(pvs, list):
                if pvs.startswith(KEY.LOCAL_PREFIX):
                    print "monitor.add(%s): Adding local var" % pvs
                    local_flag = True
                elif pvs.startswith(KEY.LOCAL_PREFIX_2):
                    print "monitor.add(%s): Adding local var" % pvs
                    local_flag = True

        if local_flag:
            # Assume this is a "local" variable (i.e., not EPICS)
            if self.local_dict.has_key(pvs):
                print "monitor.add(%s): WARNING: already have local var: ignoring new value" % pvs
                return

            if var == EPICS_PV_MARKER:
                var = None

            self.local_dict[pvs] = var
            self.pv_connected[pvs] = True
            return

        if not isinstance(pvs, list):
            pvs = [pvs]

        for name in pvs:
            if not self.pv_dict.has_key(name):
                if self.local_dict.has_key(name):
                    raise ValueError("PV '%s' already a local var" % name)

                print "monitor.add(%s): Adding PV" % name
                self.pv_dict[name] = None
            else:
                # Already know about this PV
                print "monitor.add(%s): WARNING: Already know about PV" % name

    def connection_callback(self, *args, **kwargs):

        # print "MONITOR: connection callback called"
        # print "ARGS", args
        # print "KWARGS", kwargs

        name = unicode(kwargs.get(KEY.PV_NAME))
        connected = kwargs.get(KEY.PV_CONNECTED)
        self.pv_connected[name] = connected

        # print "monitor.connection_callback(): emitting SIGNAL(%s)" % name
        self.emit(self.SIGNAL, name)

    def pv_callback(self, **kwargs):
        """
        Handle PV update callback.
        """

        # Determine the name of the PV
        name = unicode(kwargs.get(KEY.PV_NAME))

        # Store the results returned in the callback kwargs in a local dict
        self.pv_data[name] = kwargs
        self.emit(self.SIGNAL, name)

    def get_connected(self, name):
        return self.pv_connected.get(name, None)

    def get_data(self, name):
        data = self.pv_data.get(name, None)
        if data:
            data[KEY.PV_CONNECTED] = self.get_connected(name)

        return data

    def get_value(self, name, default=None, timeout=0):

        if default is not None:
            timeout = 0

        elapsed_time = 0
        while True:
            result = self._get_value_internal(name)

            if result is not None or elapsed_time >= timeout:
                if result is None:
                    result = default
                if timeout > 0 and result is None and default is not None:
                    print("MONITOR: Timeout waiting for '%s'" % name)
                break

            time.sleep(0.1)
            elapsed_time += 0.1

        return result

    def _get_value_internal(self, name):

        if not self.pv_connected.get(name, False):
            return None

        data = self.pv_data.get(name)

        if data:
            return data.get('value')

        data = self.local_dict.get(name)

        if data is not None:
            if isinstance(data, dict):
                return copy.copy(data)
            return data

        #print "monitor.get_value(): Failed to find results for >%s< (%s)" % \
        #      (name, type(name))
        #
        #for key, value in self.pv_data.iteritems():
        #    print "These are the EPICs keys: >%s<" % key
        #    print "this is the value", value
        #
        #for key, value in self.local_dict.iteritems():
        #    print "These are the LOCAL keys: >%s< (%s)" % (key, type(key))
        #    print "this is the value", value

    def disconnect(self, caller):

        print "MONITOR: disconnect(%s) ID: %d" % (caller, id(caller))

        # Remove all callbacks that were registered by the caller
        for name, callbacks in self.callback_dict.iteritems():
            updated = [c for c in callbacks if id(c.__self__) != id(caller)]
            self.callback_dict[name] = updated

        delete_list = []
        for name, validator in self.validator_dict.iteritems():
            if validator and id(validator.__self__) == id(caller):
                delete_list.append(name)

        for pv in delete_list:
            self.validator_dict[pv] = None

    def disconnect_handler(self, name, func):

        name = unicode(name)
        callbacks = self.callback_dict.get(name, [])

        try:
            callbacks.remove(func)
            self.callback_dict[name] = callbacks
        except:
            pass

    def connect(self, name, func):
        """
        This registers a callback that is called whenever a
        variable value changes (or monitor.start() is called)
        """
        name = unicode(name)

        # Get list of registered callbacks
        callbacks = self.callback_dict.get(name, [])

        # print "MONITOR: PV: '%s': Adding callback %s" % (name, repr(func))

        # Add the specified function to the list of callbacks to call
        callbacks.append(func)

        # Filter out duplicate callbacks
        self.callback_dict[name] = list(set(callbacks))

    def validator(self, name, func):
        name = unicode(name)

        # Get list of registered callbacks
        validator = self.validator_dict.get(name)
        if validator:
            print "MONITOR: Warning, PV '%s' already has a validator; overwriting" % name

        # print "MONITOR: PV: '%s': Adding validator %s" % (name, repr(func))
        self.validator_dict[name] = func