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