def AddDevice(self, axis):
     self._log.debug('AddDevice(%d): entering...' % axis)
     idx = axis - 1
     func_generator = FunctionGenerator()
     func_generator.initial_domain = SynchDomain.Time
     func_generator.active_domain = SynchDomain.Time
     self.tg[idx] = func_generator
 def AddDevice(self, axis):
     self._log.debug('AddDevice(%d): entering...' % axis)
     idx = axis - 1
     func_generator = FunctionGenerator()
     func_generator.initial_domain = SynchDomain.Time
     func_generator.active_domain = SynchDomain.Time
     self.tg[idx] = func_generator
Exemplo n.º 3
0
 def __init__(self, main_element, name="Synchronization"):
     PoolAction.__init__(self, main_element, name)
     # Even if rest of Sardana is using "." in logger names use "-" as
     # sepator. This is in order to avoid confusion about the logger
     # hierary - by default python logging use "." to indicate loggers'
     # hirarchy in case parent-children relation is established between the
     # loggers.
     # TODO: review if it is possible in Sardana to use a common separator.
     soft_synch_name = main_element.name + "-SoftSynch"
     self._synch_soft = FunctionGenerator(name=soft_synch_name)
     self._listener = None
Exemplo n.º 4
0
 def __init__(self, main_element, name="Synchronization"):
     PoolAction.__init__(self, main_element, name)
     # Even if rest of Sardana is using "." in logger names use "-" as
     # sepator. This is in order to avoid confusion about the logger
     # hierary - by default python logging use "." to indicate loggers'
     # hirarchy in case parent-children relation is established between the
     # loggers.
     # TODO: review if it is possible in Sardana to use a common separator.
     soft_synch_name = main_element.name + "-SoftSynch"
     self._synch_soft = FunctionGenerator(name=soft_synch_name)
     self._listener = None
Exemplo n.º 5
0
 def AddDevice(self, axis):
     self._log.debug('AddDevice(%d): entering...' % axis)
     idx = axis - 1
     self.device.setoutput(axis, True)
     func_generator = FunctionGenerator()
     func_generator.initial_domain = SynchDomain.Time
     func_generator.active_domain = SynchDomain.Time
     cb = RasPiDOCallback(self.device, axis)
     func_generator.add_listener(cb)
     self.tg[idx] = func_generator
     self.cbs[idx] = cb
     self._log.debug('AddDevice(%d): leaving...' % axis)
Exemplo n.º 6
0
 def AddDevice(self, axis):
     self._log.debug('AddDevice(%d): entering...' % axis)
     idx = axis - 1
     func_generator = FunctionGenerator()
     func_generator.initial_domain = SynchDomain.Time
     func_generator.active_domain = SynchDomain.Time
     cb = RasPiTangoDOCallback(self, axis)
     func_generator.add_listener(cb)
     self.tg[idx] = func_generator
     self.cbs[idx] = cb
     self.state[idx] = State.On
     self.status[idx] = "Stopped"
     self._log.debug("AddDevice(%d): leaving..." % axis)
Exemplo n.º 7
0
 def setUp(self):
     self.thread_pool = ThreadPool("TestThreadPool", Psize=2)
     self.func_generator = FunctionGenerator()
     self.event = Event()
     self.listener = Listener()
     self.func_generator.add_listener(self.listener)
Exemplo n.º 8
0
class FuncGeneratorTestCase(TestCase):
    def setUp(self):
        self.thread_pool = ThreadPool("TestThreadPool", Psize=2)
        self.func_generator = FunctionGenerator()
        self.event = Event()
        self.listener = Listener()
        self.func_generator.add_listener(self.listener)

    def _done(self, _):
        self.event.set()
        self.event.clear()

    def test_sleep(self):
        delta = 0
        if os.name == "nt":
            delta = 0.02
        for i in [0.01, 0.13, 1.2]:
            stmt = "fg.sleep(%f)" % i
            setup = "from sardana.util.funcgenerator import FunctionGenerator;\
                     fg = FunctionGenerator()"

            period = timeit.timeit(stmt, setup, number=1)
            period_ok = i
            msg = "sleep period: %f, expected: %f +/- %f" % (period, period_ok,
                                                             delta)
            self.assertAlmostEqual(period, period_ok, delta=0.02, msg=msg)

    def test_run_time(self):
        self.func_generator.initial_domain = SynchDomain.Time
        self.func_generator.set_configuration(configuration_positive)
        self.func_generator.start()
        self.thread_pool.add(self.func_generator.run, self._done)
        self.event.wait(100)
        active_event_ids = self.listener.active_event_ids
        active_event_ids_ok = range(0, 10)
        msg = "Received active event ids: %s, expected: %s" % (
            active_event_ids, active_event_ids_ok)
        self.assertListEqual(active_event_ids, active_event_ids_ok, msg)

    def test_stop_time(self):
        self.func_generator.initial_domain = SynchDomain.Time
        self.func_generator.set_configuration(configuration_positive)
        self.func_generator.start()
        self.thread_pool.add(self.func_generator.run, self._done)
        while not self.func_generator.is_running():
            time.sleep(0.01)
        # starting timer that will stop generation
        Timer(0.2, self.func_generator.stop).start()
        self.event.wait(100)
        self.assertFalse(self.func_generator.is_running(), "Stopping failed")
        self.listener.init()
        self.test_run_time()

    def test_run_position_negative(self):
        position = Position()
        position.add_listener(self.func_generator)
        self.func_generator.initial_domain = SynchDomain.Position
        self.func_generator.active_domain = SynchDomain.Position
        self.func_generator.direction = -1
        self.func_generator.set_configuration(configuration_negative)
        self.func_generator.start()
        self.thread_pool.add(self.func_generator.run, self._done)
        while not self.func_generator.is_running():
            time.sleep(0.1)
        self.thread_pool.add(position.run, None, 0, -2, -.01)
        self.event.wait(3)
        position.remove_listener(self.func_generator)
        active_event_ids = self.listener.active_event_ids
        active_event_ids_ok = range(0, 10)
        msg = "Received active event ids: %s, expected: %s" % (
            active_event_ids, active_event_ids_ok)
        self.assertListEqual(active_event_ids, active_event_ids_ok, msg)

    def test_run_position_positive(self):
        position = Position()
        position.add_listener(self.func_generator)
        self.func_generator.initial_domain = SynchDomain.Position
        self.func_generator.active_domain = SynchDomain.Position
        self.func_generator.direction = 1
        self.func_generator.set_configuration(configuration_positive)
        self.func_generator.start()
        self.thread_pool.add(self.func_generator.run, self._done)
        while not self.func_generator.is_running():
            time.sleep(0.1)
        self.thread_pool.add(position.run, None, 0, 2, .01)
        self.event.wait(3)
        position.remove_listener(self.func_generator)
        active_event_ids = self.listener.active_event_ids
        active_event_ids_ok = range(0, 10)
        msg = "Received active event ids: %s, expected: %s" % (
            active_event_ids, active_event_ids_ok)
        self.assertListEqual(active_event_ids, active_event_ids_ok, msg)

    def test_configuration_position(self):
        self.func_generator.initial_domain = SynchDomain.Position
        self.func_generator.active_domain = SynchDomain.Position
        self.func_generator.set_configuration(configuration_negative)
        active_events = self.func_generator.active_events
        active_events_ok = numpy.arange(0, -2, -0.2).tolist()
        msg = "Active events are wrong: %s" % active_events
        for a, b in zip(active_events, active_events_ok):
            self.assertAlmostEqual(a, b, 10, msg)
        passive_events = self.func_generator.passive_events
        passive_events_ok = numpy.arange(-.1, -2.1, -0.2).tolist()
        msg = "Passive events are wrong: %s" % passive_events
        for a, b in zip(passive_events, passive_events_ok):
            self.assertAlmostEqual(a, b, 10, msg)

    def test_configuration_time(self):
        self.func_generator.initial_domain = SynchDomain.Time
        self.func_generator.active_domain = SynchDomain.Time
        self.func_generator.set_configuration(configuration_positive)
        active_events = self.func_generator.active_events
        active_events_ok = numpy.arange(.3, .5, 0.02).tolist()
        msg = ("Active events mismatch, received: %s, expected: %s" %
               (active_events, active_events_ok))
        for a, b in zip(active_events, active_events_ok):
            self.assertAlmostEqual(a, b, 10, msg)
        passive_events = self.func_generator.passive_events
        passive_events_ok = numpy.arange(.31, 0.51, 0.02).tolist()
        msg = ("Passive events mismatch, received: %s, expected: %s" %
               (passive_events, passive_events_ok))
        for a, b in zip(passive_events, passive_events_ok):
            self.assertAlmostEqual(a, b, 10, msg)

    def test_configuration_default(self):
        self.func_generator.set_configuration(configuration_positive)
        active_events = self.func_generator.active_events
        active_events_ok = numpy.arange(0, 2, 0.2).tolist()
        msg = ("Active events mismatch, received: %s, expected: %s" %
               (active_events, active_events_ok))
        for a, b in zip(active_events, active_events_ok):
            self.assertAlmostEqual(a, b, 10, msg)
        passive_events = self.func_generator.passive_events
        passive_events_ok = numpy.arange(.31, .51, 0.02).tolist()
        msg = ("Passive events mismatch, received: %s, expected: %s" %
               (passive_events, passive_events_ok))
        for a, b in zip(passive_events, passive_events_ok):
            self.assertAlmostEqual(a, b, 10, msg)

    def tearDown(self):
        self.func_generator.remove_listener(self.listener)
Exemplo n.º 9
0
 def __init__(self, main_element, name="Synchronization"):
     PoolAction.__init__(self, main_element, name)
     self._synch_soft = FunctionGenerator()
     self._listener = None
Exemplo n.º 10
0
class PoolSynchronization(PoolAction):
    '''Action class responsible for trigger/gate generation
    '''
    def __init__(self, main_element, name="Synchronization"):
        PoolAction.__init__(self, main_element, name)
        self._synch_soft = FunctionGenerator()
        self._listener = None

    def add_listener(self, listener):
        self._listener = listener

    def start_action(self, *args, **kwargs):
        '''Start action method. Expects the following kwargs:
        - config - dictionary containing measurement group configuration
        - synchronization - list of dictionaries containing information about
          the expected synchronization
        - moveable (optional)- moveable object used as the synchronization
          source in the Position domain
        - monitor (optional) - counter/timer object used as the synchronization
          source in the Monitor domain
        '''
        cfg = kwargs['config']
        synchronization = kwargs.get('synchronization')
        moveable = kwargs.get('moveable')
        ctrls_config = cfg.get('controllers')
        pool_ctrls = ctrls_config.keys()

        # Prepare a dictionary with the involved channels
        self._channels = channels = {}
        for pool_ctrl in pool_ctrls:
            pool_ctrl_data = ctrls_config[pool_ctrl]
            elements = pool_ctrl_data['channels']

            for element, element_info in elements.items():
                channel = TGChannel(element, info=element_info)
                channels[element] = channel

        with ActionContext(self):

            # loads synchronization description
            for pool_ctrl in pool_ctrls:
                ctrl = pool_ctrl.ctrl
                pool_ctrl_data = ctrls_config[pool_ctrl]
                ctrl.PreSynchAll()
                elements = pool_ctrl_data['channels']
                for element in elements:
                    axis = element.axis
                    ret = ctrl.PreSynchOne(axis, synchronization)
                    if not ret:
                        msg = ("%s.PreSynchOne(%d) returns False" %
                               (pool_ctrl.name, axis))
                        raise Exception(msg)
                    ctrl.SynchOne(axis, synchronization)
                ctrl.SynchAll()

            # attaching listener (usually acquisition action)
            # to the software trigger gate generator
            if self._listener is not None:
                self._synch_soft.set_configuration(synchronization)
                self._synch_soft.add_listener(self._listener)
                remove_acq_listener = partial(self._synch_soft.remove_listener,
                                              self._listener)
                self.add_finish_hook(remove_acq_listener, False)
                self._synch_soft.add_listener(
                    self.main_element.on_element_changed)
                remove_mg_listener = partial(self._synch_soft.remove_listener,
                                             self.main_element)
                self.add_finish_hook(remove_mg_listener, False)
            # subscribing to the position change events to generate events
            # in position domain
            if moveable is not None:
                position = moveable.get_position_attribute()
                position.add_listener(self._synch_soft)
                remove_pos_listener = partial(position.remove_listener,
                                              self._synch_soft)
                self.add_finish_hook(remove_pos_listener, False)

            # PreStartAll on all controllers
            for pool_ctrl in pool_ctrls:
                pool_ctrl.ctrl.PreStartAll()

            # PreStartOne & StartOne on all elements
            for pool_ctrl in pool_ctrls:
                ctrl = pool_ctrl.ctrl
                pool_ctrl_data = ctrls_config[pool_ctrl]
                elements = pool_ctrl_data['channels']
                for element in elements:
                    axis = element.axis
                    channel = channels[element]
                    ret = ctrl.PreStartOne(axis)
                    if not ret:
                        raise Exception("%s.PreStartOne(%d) returns False" %
                                        (pool_ctrl.name, axis))
                    ctrl.StartOne(axis)

            # set the state of all elements to inform their listeners
            for channel in channels:
                channel.set_state(State.Moving, propagate=2)

            # StartAll on all controllers
            for pool_ctrl in pool_ctrls:
                pool_ctrl.ctrl.StartAll()

            if self._listener is not None:
                self._synch_soft.start()
                get_thread_pool().add(self._synch_soft.run)

    def is_triggering(self, states):
        """Determines if we are triggering or if the triggering has ended
        based on the states returned by the controller(s) and the software
        TG generation.

        :param states: a map containing state information as returned by
                       read_state_info: ((state, status), exception_error)
        :type states: dict<PoolElement, tuple(tuple(int, str), str))
        :return: returns True if is triggering or False otherwise
        :rtype: bool"""
        for elem in states:
            state_info_idx = 0
            state_idx = 0
            state_tggate = states[elem][state_info_idx][state_idx]
            if self._is_in_action(state_tggate):
                return True
        return False

    @DebugIt()
    def action_loop(self):
        '''action_loop method
        '''
        states = {}
        for element in self._channels:
            states[element] = None

        # Triggering loop
        # TODO: make nap configurable (see motion or acquisition loops)
        nap = 0.2
        while True:
            self.read_state_info(ret=states)
            if not self.is_triggering(states):
                break
            time.sleep(nap)

        # Set element states after ending the triggering
        for triggerelement, state_info in states.items():
            with triggerelement:
                triggerelement.clear_operation()
                state_info = triggerelement._from_ctrl_state_info(state_info)
                triggerelement.set_state_info(state_info, propagate=2)

        # wait for software synchronizer to finish
        if self._listener is not None:
            while True:
                if not self._synch_soft.is_started():
                    break
                time.sleep(0.01)
Exemplo n.º 11
0
class PoolSynchronization(PoolAction):
    """Synchronization action.

    It coordinates trigger/gate elements and software synchronizer.

    .. todo: Think of moving the ready/busy mechanism to PoolAction
    """

    def __init__(self, main_element, name="Synchronization"):
        PoolAction.__init__(self, main_element, name)
        # Even if rest of Sardana is using "." in logger names use "-" as
        # sepator. This is in order to avoid confusion about the logger
        # hierary - by default python logging use "." to indicate loggers'
        # hirarchy in case parent-children relation is established between the
        # loggers.
        # TODO: review if it is possible in Sardana to use a common separator.
        soft_synch_name = main_element.name + "-SoftSynch"
        self._synch_soft = FunctionGenerator(name=soft_synch_name)
        self._listener = None
        self._ready = threading.Event()
        self._ready.set()

    def _is_ready(self):
        return self._ready.is_set()

    def _wait(self, timeout=None):
        return self._ready.wait(timeout)

    def _set_ready(self, _=None):
        self._ready.set()

    def _is_busy(self):
        return not self._ready.is_set()

    def _set_busy(self):
        self._ready.clear()

    def add_listener(self, listener):
        self._listener = listener

    def start_action(self, ctrls, synch_description, moveable=None,
                     sw_synch_initial_domain=None, *args, **kwargs):
        """Start synchronization action.

        :param ctrls: list of enabled trigger/gate controllers
        :type ctrls: list
        :param synch_description: synchronization description
        :type synch_description:
         :class:`~sardana.pool.poolsynchronization.SynchDescription`
        :param moveable: (optional) moveable object used as the
         synchronization source in the Position domain
        :type moveable: :class:`~sardna.pool.poolmotor.PoolMotor` or
         :class:`~sardana.pool.poolpseudomotor.PoolPseudoMotor`
        :param sw_synch_initial_domain: (optional) - initial domain for
         software synchronizer, can be either
         :obj:`~sardana.pool.pooldefs.SynchDomain.Time` or
         :obj:`~sardana.pool.pooldefs.SynchDomain.Position`
        """
        with ActionContext(self):

            # loads synchronization description
            for ctrl in ctrls:
                pool_ctrl = ctrl.element
                pool_ctrl.ctrl.PreSynchAll()
                for channel in ctrl.get_channels(enabled=True):
                    axis = channel.axis
                    ret = pool_ctrl.ctrl.PreSynchOne(axis, synch_description)
                    if not ret:
                        msg = ("%s.PreSynchOne(%d) returns False" %
                               (ctrl.name, axis))
                        raise Exception(msg)
                    pool_ctrl.ctrl.SynchOne(axis, synch_description)
                pool_ctrl.ctrl.SynchAll()

            # attaching listener (usually acquisition action)
            # to the software trigger gate generator
            if self._listener is not None:
                if sw_synch_initial_domain is not None:
                    self._synch_soft.initial_domain = sw_synch_initial_domain
                self._synch_soft.set_configuration(synch_description)
                self._synch_soft.add_listener(self._listener)
                remove_acq_listener = partial(self._synch_soft.remove_listener,
                                              self._listener)
                self.add_finish_hook(remove_acq_listener, False)
                self._synch_soft.add_listener(
                    self.main_element.on_element_changed)
                remove_mg_listener = partial(self._synch_soft.remove_listener,
                                             self.main_element)
                self.add_finish_hook(remove_mg_listener, False)
            # subscribing to the position change events to generate events
            # in position domain
            if moveable is not None:
                position = moveable.get_position_attribute()
                position.add_listener(self._synch_soft)
                remove_pos_listener = partial(position.remove_listener,
                                              self._synch_soft)
                self.add_finish_hook(remove_pos_listener, False)

            # start software synchronizer
            if self._listener is not None:
                self._synch_soft.start()
                get_thread_pool().add(self._synch_soft.run)

            # PreStartAll on all controllers
            for ctrl in ctrls:
                pool_ctrl = ctrl.element
                pool_ctrl.ctrl.PreStartAll()

            # PreStartOne & StartOne on all elements
            for ctrl in ctrls:
                pool_ctrl = ctrl.element
                for channel in ctrl.get_channels(enabled=True):
                    axis = channel.axis
                    ret = pool_ctrl.ctrl.PreStartOne(axis)
                    if not ret:
                        raise Exception("%s.PreStartOne(%d) returns False"
                                        % (pool_ctrl.name, axis))
                    pool_ctrl.ctrl.StartOne(axis)

            # set the state of all elements to inform their listeners
            self._channels = []
            for ctrl in ctrls:
                for channel in ctrl.get_channels(enabled=True):
                    channel.set_state(State.Moving, propagate=2)
                    self._channels.append(channel)

            # StartAll on all controllers
            for ctrl in ctrls:
                pool_ctrl = ctrl.element
                pool_ctrl.ctrl.StartAll()

    def is_triggering(self, states):
        """Determines if we are synchronizing or not based on the states
        returned by the controller(s) and the software synchronizer.

        :param states: a map containing state information as returned by
                       read_state_info: ((state, status), exception_error)
        :type states: dict<PoolElement, tuple(tuple(int, str), str))
        :return: returns True if is triggering or False otherwise
        :rtype: bool
        """
        for elem in states:
            state_info_idx = 0
            state_idx = 0
            state_tggate = states[elem][state_info_idx][state_idx]
            if self._is_in_action(state_tggate):
                return True
        return False

    @DebugIt()
    def action_loop(self):
        """action_loop method
        """
        states = {}
        for channel in self._channels:
            element = channel.element
            states[element] = None

        # Triggering loop
        # TODO: make nap configurable (see motion or acquisition loops)
        nap = 0.01
        while True:
            self.read_state_info(ret=states)
            if not self.is_triggering(states):
                break
            time.sleep(nap)

        # Set element states after ending the triggering
        for element, state_info in list(states.items()):
            with element:
                element.clear_operation()
                state_info = element._from_ctrl_state_info(state_info)
                element.set_state_info(state_info, propagate=2)

        # wait for software synchronizer to finish
        if self._listener is not None:
            while True:
                if not self._synch_soft.is_started():
                    break
                time.sleep(0.01)
Exemplo n.º 12
0
class PoolSynchronization(PoolAction):
    """Synchronization action.

    It coordinates trigger/gate elements and software synchronizer.
    """

    def __init__(self, main_element, name="Synchronization"):
        PoolAction.__init__(self, main_element, name)
        # Even if rest of Sardana is using "." in logger names use "-" as
        # sepator. This is in order to avoid confusion about the logger
        # hierary - by default python logging use "." to indicate loggers'
        # hirarchy in case parent-children relation is established between the
        # loggers.
        # TODO: review if it is possible in Sardana to use a common separator.
        soft_synch_name = main_element.name + "-SoftSynch"
        self._synch_soft = FunctionGenerator(name=soft_synch_name)
        self._listener = None

    def add_listener(self, listener):
        self._listener = listener

    def start_action(self, ctrls, synchronization, moveable=None,
                     sw_synch_initial_domain=None, *args, **kwargs):
        """Start synchronization action.

        :param ctrls: list of enabled trigger/gate controllers
        :type ctrls: list
        :param synchronization: synchronization description
        :type synchronization:
         :class:`~sardana.pool.poolsynchronization.SynchronizationDescription`
        :param moveable: (optional) moveable object used as the
         synchronization source in the Position domain
        :type moveable: :class:`~sardna.pool.poolmotor.PoolMotor` or
         :class:`~sardana.pool.poolpseudomotor.PoolPseudoMotor`
        :param sw_synch_initial_domain: (optional) - initial domain for
         software synchronizer, can be either
         :obj:`~sardana.pool.pooldefs.SynchDomain.Time` or
         :obj:`~sardana.pool.pooldefs.SynchDomain.Position`
        """
        with ActionContext(self):

            # loads synchronization description
            for ctrl in ctrls:
                pool_ctrl = ctrl.element
                pool_ctrl.ctrl.PreSynchAll()
                for channel in ctrl.get_channels(enabled=True):
                    axis = channel.axis
                    ret = pool_ctrl.ctrl.PreSynchOne(axis, synchronization)
                    if not ret:
                        msg = ("%s.PreSynchOne(%d) returns False" %
                               (ctrl.name, axis))
                        raise Exception(msg)
                    pool_ctrl.ctrl.SynchOne(axis, synchronization)
                pool_ctrl.ctrl.SynchAll()

            # attaching listener (usually acquisition action)
            # to the software trigger gate generator
            if self._listener is not None:
                if sw_synch_initial_domain is not None:
                    self._synch_soft.initial_domain = sw_synch_initial_domain
                self._synch_soft.set_configuration(synchronization)
                self._synch_soft.add_listener(self._listener)
                remove_acq_listener = partial(self._synch_soft.remove_listener,
                                              self._listener)
                self.add_finish_hook(remove_acq_listener, False)
                self._synch_soft.add_listener(
                    self.main_element.on_element_changed)
                remove_mg_listener = partial(self._synch_soft.remove_listener,
                                             self.main_element)
                self.add_finish_hook(remove_mg_listener, False)
            # subscribing to the position change events to generate events
            # in position domain
            if moveable is not None:
                position = moveable.get_position_attribute()
                position.add_listener(self._synch_soft)
                remove_pos_listener = partial(position.remove_listener,
                                              self._synch_soft)
                self.add_finish_hook(remove_pos_listener, False)

            # start software synchronizer
            if self._listener is not None:
                self._synch_soft.start()
                get_thread_pool().add(self._synch_soft.run)

            # PreStartAll on all controllers
            for ctrl in ctrls:
                pool_ctrl = ctrl.element
                pool_ctrl.ctrl.PreStartAll()

            # PreStartOne & StartOne on all elements
            for ctrl in ctrls:
                pool_ctrl = ctrl.element
                for channel in ctrl.get_channels(enabled=True):
                    axis = channel.axis
                    ret = pool_ctrl.ctrl.PreStartOne(axis)
                    if not ret:
                        raise Exception("%s.PreStartOne(%d) returns False"
                                        % (pool_ctrl.name, axis))
                    pool_ctrl.ctrl.StartOne(axis)

            # set the state of all elements to inform their listeners
            self._channels = []
            for ctrl in ctrls:
                for channel in ctrl.get_channels(enabled=True):
                    channel.set_state(State.Moving, propagate=2)
                    self._channels.append(channel)

            # StartAll on all controllers
            for ctrl in ctrls:
                pool_ctrl = ctrl.element
                pool_ctrl.ctrl.StartAll()

    def is_triggering(self, states):
        """Determines if we are synchronizing or not based on the states
        returned by the controller(s) and the software synchronizer.

        :param states: a map containing state information as returned by
                       read_state_info: ((state, status), exception_error)
        :type states: dict<PoolElement, tuple(tuple(int, str), str))
        :return: returns True if is triggering or False otherwise
        :rtype: bool
        """
        for elem in states:
            state_info_idx = 0
            state_idx = 0
            state_tggate = states[elem][state_info_idx][state_idx]
            if self._is_in_action(state_tggate):
                return True
        return False

    @DebugIt()
    def action_loop(self):
        """action_loop method
        """
        states = {}
        for channel in self._channels:
            element = channel.element
            states[element] = None

        # Triggering loop
        # TODO: make nap configurable (see motion or acquisition loops)
        nap = 0.2
        while True:
            self.read_state_info(ret=states)
            if not self.is_triggering(states):
                break
            time.sleep(nap)

        # Set element states after ending the triggering
        for element, state_info in states.items():
            with element:
                element.clear_operation()
                state_info = element._from_ctrl_state_info(state_info)
                element.set_state_info(state_info, propagate=2)

        # wait for software synchronizer to finish
        if self._listener is not None:
            while True:
                if not self._synch_soft.is_started():
                    break
                time.sleep(0.01)