class AcquisitionTestCase(BasePoolTestCase): def setUp(self): """Create a Controller, TriggerGate and PoolSynchronization objects from dummy configurations. """ BasePoolTestCase.setUp(self) self.l = AttributeListener() self.channel_names = [] def createPoolSynchronization(self, tg_list): self.main_element = FakeElement(self.pool) self.tggeneration = PoolSynchronization(self.main_element) for tg in tg_list: self.tggeneration.add_element(tg) self.tggeneration.add_listener(self) def hw_continuous_acquisition(self, offset, active_interval, passive_interval, repetitions, integ_time): """Executes measurement running the TGGeneration and Acquisition actions according the test parameters. Checks the lengths of the acquired data. """ # obtaining elements created in the BasePoolTestCase.setUp tg = self.tgs[self.tg_elem_name] tg_ctrl = tg.get_controller() # crating configuration for TGGeneration tg_cfg = createPoolSynchronizationConfiguration((tg_ctrl,), ((tg,),)) # creating PoolSynchronization action self.createPoolSynchronization([tg]) channels = [] for name in self.channel_names: channels.append(self.cts[name]) ct_ctrl = self.ctrls[self.chn_ctrl_name] # add_listeners self.addListeners(channels) # creating acquisition configurations self.hw_acq_cfg = createCTAcquisitionConfiguration((ct_ctrl,), (channels,)) # creating acquisition actions self.hw_acq = PoolAcquisitionHardware(channels[0]) for channel in channels: self.hw_acq.add_element(channel) # get the current number of jobs jobs_before = get_thread_pool().qsize ct_ctrl.set_ctrl_par('synchronization', AcqSynch.HardwareTrigger) hw_acq_args = () hw_acq_kwargs = { 'integ_time': integ_time, 'repetitions': repetitions, 'config': self.hw_acq_cfg, } self.hw_acq.run(hw_acq_args, **hw_acq_kwargs) tg_args = () tg_kwargs = { 'offset': offset, 'active_interval': active_interval, 'passive_interval': passive_interval, 'repetitions': repetitions, 'config': tg_cfg } self.tggeneration.run(*tg_args, **tg_kwargs) # waiting for acquisition and tggeneration to finish while self.hw_acq.is_running() or self.tggeneration.is_running(): time.sleep(1) self.do_asserts(self.channel_names, repetitions, jobs_before) def hw_step_acquisition(self, repetitions, integ_time): """Executes measurement running the TGGeneration and Acquisition actions according the test parameters. Checks the lengths of the acquired data. """ channels = [] for name in self.channel_names: channels.append(self.cts[name]) ct_ctrl = self.ctrls[self.chn_ctrl_name] # creating acquisition configurations self.acq_cfg = createCTAcquisitionConfiguration((ct_ctrl,), (channels,)) # creating acquisition actions main_element = FakeElement(self.pool) self.ct_acq = PoolAcquisitionSoftware(main_element) for channel in channels: self.ct_acq.add_element(channel) ct_ctrl.set_ctrl_par('synchronization', AcqSynch.SoftwareTrigger) ct_acq_args = () ct_acq_kwargs = { 'integ_time': integ_time, 'repetitions': repetitions, 'config': self.acq_cfg, } self.ct_acq.run(ct_acq_args, **ct_acq_kwargs) # waiting for acquisition while self.ct_acq.is_running(): time.sleep(0.02) for channel in channels: name = channel.name value = channel.value.value print 'channel: %s = %s' % (name, value) msg = ('Value for channel %s is of type %s, should be <float>' % (name, type(value))) self.assertIsInstance(value, float, msg) def addListeners(self, chn_list): for chn in chn_list: chn.add_listener(self.l) def do_asserts(self, channel_names, repetitions, jobs_before): # print acquisition records table = self.l.get_table() header = table.dtype.names print header n_rows = table.shape[0] for row in xrange(n_rows): print row, table[row] # checking if all channels produced data for channel in channel_names: msg = 'data from channel %s were not acquired' % channel self.assertIn(channel, header, msg) # checking if all the data were acquired for ch_name in header: ch_data_len = len(table[ch_name]) msg = 'length of data for channel %s is %d and should be %d' %\ (ch_name, ch_data_len, repetitions) self.assertEqual(ch_data_len, repetitions, msg) # checking if there are no pending jobs jobs_after = get_thread_pool().qsize msg = ('there are %d jobs pending to be done after the acquisition ' + '(before: %d)') % (jobs_after, jobs_before) self.assertEqual(jobs_before, jobs_after, msg) def tearDown(self): BasePoolTestCase.tearDown(self) self.l = None self.channel_names = None
class PoolAcquisition(PoolAction): def __init__(self, main_element, name="Acquisition"): PoolAction.__init__(self, main_element, name) zerodname = name + ".0DAcquisition" hwname = name + ".HardwareAcquisition" swname = name + ".SoftwareAcquisition" synchname = name + ".Synchronization" self._sw_acq_config = None self._0d_config = None self._0d_acq = Pool0DAcquisition(main_element, name=zerodname) self._sw_acq = PoolAcquisitionSoftware(main_element, name=swname) self._hw_acq = PoolAcquisitionHardware(main_element, name=hwname) self._synch = PoolSynchronization(main_element, name=synchname) def set_sw_config(self, config): self._sw_acq_config = config def set_0d_config(self, config): self._0d_config = config def event_received(self, *args, **kwargs): timestamp = time.time() _, type_, value = args name = type_.name if name == "state": return t_fmt = '%Y-%m-%d %H:%M:%S.%f' t_str = datetime.datetime.fromtimestamp(timestamp).strftime(t_fmt) msg = '%s event with id: %d received at: %s' % (name, value, t_str) self.debug(msg) if name == "active": # this code is not thread safe, but for the moment we assume that # only one EventGenerator will work at the same time if self._sw_acq_config: if self._sw_acq._is_started() or self._sw_acq.is_running(): msg = ('Skipping trigger: software acquisition is still' ' in progress.') self.debug(msg) return else: self.debug('Executing software acquisition.') args = () kwargs = self._sw_acq_config kwargs['synch'] = True kwargs['idx'] = value self._sw_acq._started = True get_thread_pool().add(self._sw_acq.run, *args, **kwargs) if self._0d_config: if self._0d_acq._is_started() or self._0d_acq.is_running(): msg = ('Skipping trigger: ZeroD acquisition is still in' ' progress.') self.debug(msg) return else: self.debug('Executing ZeroD acquisition.') args = () kwargs = self._0d_config kwargs['synch'] = True kwargs['idx'] = value self._0d_acq._started = True self._0d_acq._stopped = False self._0d_acq._aborted = False get_thread_pool().add(self._0d_acq.run, *args, **kwargs) elif name == "passive": if self._0d_config and (self._0d_acq._is_started() or self._0d_acq.is_running()): self.debug('Stopping ZeroD acquisition.') self._0d_acq.stop_action() def is_running(self): return self._0d_acq.is_running() or\ self._sw_acq.is_running() or\ self._hw_acq.is_running() or\ self._synch.is_running() def run(self, *args, **kwargs): for elem in self.get_elements(): elem.put_state(None) # TODO: temporarily clear value buffers at the beginning of the # acquisition instead of doing it in the finish hook of each # acquisition sub-actions. See extensive explanation in the # constructor of PoolAcquisitionBase. try: elem.clear_value_buffer() except AttributeError: continue # clean also the pseudo counters, even the ones that do not # participate directly in the acquisition for pseudo_elem in elem.get_pseudo_elements(): pseudo_elem.clear_value_buffer() config = kwargs['config'] synchronization = kwargs["synchronization"] integ_time = extract_integ_time(synchronization) repetitions = extract_repetitions(synchronization) # TODO: this code splits the global mg configuration into # experimental channels triggered by hw and experimental channels # triggered by sw. Refactor it!!!! (hw_acq_cfg, sw_acq_cfg, zerod_acq_cfg) = split_MGConfigurations(config) synch_cfg, _ = getTGConfiguration(config) # starting continuous acquisition only if there are any controllers if len(hw_acq_cfg['controllers']): cont_acq_kwargs = dict(kwargs) cont_acq_kwargs['config'] = hw_acq_cfg cont_acq_kwargs['integ_time'] = integ_time cont_acq_kwargs['repetitions'] = repetitions self._hw_acq.run(*args, **cont_acq_kwargs) if len(sw_acq_cfg['controllers']) or len(zerod_acq_cfg['controllers']): self._synch.add_listener(self) if len(sw_acq_cfg['controllers']): sw_acq_kwargs = dict(kwargs) sw_acq_kwargs['config'] = sw_acq_cfg sw_acq_kwargs['integ_time'] = integ_time sw_acq_kwargs['repetitions'] = 1 self.set_sw_config(sw_acq_kwargs) if len(zerod_acq_cfg['controllers']): zerod_acq_kwargs = dict(kwargs) zerod_acq_kwargs['config'] = zerod_acq_cfg self.set_0d_config(zerod_acq_kwargs) synch_kwargs = dict(kwargs) synch_kwargs['config'] = synch_cfg self._synch.run(*args, **synch_kwargs) def _get_action_for_element(self, element): elem_type = element.get_type() if elem_type in TYPE_TIMERABLE_ELEMENTS: main_element = self.main_element channel_to_acq_synch = main_element._channel_to_acq_synch acq_synch = channel_to_acq_synch.get(element) if acq_synch in (AcqSynch.SoftwareTrigger, AcqSynch.SoftwareGate): return self._sw_acq elif acq_synch in (AcqSynch.HardwareTrigger, AcqSynch.HardwareGate): return self._hw_acq else: # by default software synchronization is in use return self._sw_acq elif elem_type == ElementType.ZeroDExpChannel: return self._0d_acq elif elem_type == ElementType.TriggerGate: return self._synch else: raise RuntimeError("Could not determine action for element %s" % element) def clear_elements(self): """Clears all elements from this action""" def add_element(self, element): """Adds a new element to this action. :param element: the new element to be added :type element: sardana.pool.poolelement.PoolElement""" action = self._get_action_for_element(element) action.add_element(element) def remove_element(self, element): """Removes an element from this action. If the element is not part of this action, a ValueError is raised. :param element: the new element to be removed :type element: sardana.pool.poolelement.PoolElement :raises: ValueError""" for action in self._get_acq_for_element(element): action.remove_element(element) def get_elements(self, copy_of=False): """Returns a sequence of all elements involved in this action. :param copy_of: If False (default) the internal container of elements is returned. If True, a copy of the internal container is returned instead :type copy_of: bool :return: a sequence of all elements involved in this action. :rtype: seq<sardana.pool.poolelement.PoolElement>""" return (self._hw_acq.get_elements() + self._sw_acq.get_elements() + self._0d_acq.get_elements() + self._synch.get_elements()) def get_pool_controller_list(self): """Returns a list of all controller elements involved in this action. :return: a list of all controller elements involved in this action. :rtype: list<sardana.pool.poolelement.PoolController>""" return self._pool_ctrl_list def get_pool_controllers(self): """Returns a dict of all controller elements involved in this action. :return: a dict of all controller elements involved in this action. :rtype: dict<sardana.pool.poolelement.PoolController, seq<sardana.pool.poolelement.PoolElement>>""" ret = {} ret.update(self._hw_acq.get_pool_controllers()) ret.update(self._sw_acq.get_pool_controllers()) ret.update(self._0d_acq.get_pool_controllers()) return ret def read_value(self, ret=None, serial=False): """Reads value information of all elements involved in this action :param ret: output map parameter that should be filled with value information. If None is given (default), a new map is created an returned :type ret: dict :param serial: If False (default) perform controller HW value requests in parallel. If True, access is serialized. :type serial: bool :return: a map containing value information per element :rtype: dict<:class:~`sardana.pool.poolelement.PoolElement`, :class:~`sardana.sardanavalue.SardanaValue`>""" # TODO: this is broken now - fix it ret = self._ct_acq.read_value(ret=ret, serial=serial) ret.update(self._0d_acq.read_value(ret=ret, serial=serial)) return ret