def test_epicssignal_set(put_complete): sim_pv = EpicsSignal(write_pv='XF:31IDA-OP{Tbl-Ax:X1}Mtr.VAL', read_pv='XF:31IDA-OP{Tbl-Ax:X1}Mtr.RBV', put_complete=put_complete) sim_pv.wait_for_connection() logging.getLogger('ophyd.signal').setLevel(logging.DEBUG) logging.getLogger('ophyd.utils.epics_pvs').setLevel(logging.DEBUG) print('tolerance=', sim_pv.tolerance) assert sim_pv.tolerance is not None start_pos = sim_pv.get() # move to +0.2 and check the status object target = sim_pv.get() + 0.2 st = sim_pv.set(target, timeout=1, settle_time=0.001) wait(st, timeout=5) assert st.done assert st.success print('status 1', st) assert abs(target - sim_pv.get()) < 0.05 # move back to -0.2, forcing a timeout with a low value target = sim_pv.get() - 0.2 st = sim_pv.set(target, timeout=1e-6) time.sleep(0.1) print('status 2', st) assert st.done assert not st.success # keep the axis in position st = sim_pv.set(start_pos) wait(st, timeout=5)
def pair_signal(cleanup, signal_test_ioc): sig = EpicsSignal(read_pv=signal_test_ioc.pvs['pair_rbv'], write_pv=signal_test_ioc.pvs['pair_set'], name='pair') cleanup.add(sig) sig.wait_for_connection() return sig
def test_epicssignal_waveform(): def update_cb(value=None, **kwargs): assert value in FakeEpicsWaveform.strings signal = EpicsSignal('readpv', string=True) signal.wait_for_connection() signal.subscribe(update_cb, event_type=signal.SUB_VALUE) assert signal.value in FakeEpicsWaveform.strings
def test_enum_strs(): sig = EpicsSignal('connects') sig.wait_for_connection() enums = ['enum_strs'] # hack this onto the FakeEpicsPV sig._read_pv.enum_strs = enums assert sig.enum_strs == enums
def test_epicssignal_sub_setpoint(cleanup, fake_motor_ioc): pvs = fake_motor_ioc.pvs pv = EpicsSignal(write_pv=pvs['setpoint'], read_pv=pvs['readback'], name='pv') cleanup.add(pv) setpoint_called = [] setpoint_meta_called = [] def sub_setpoint(old_value, value, **kwargs): setpoint_called.append((old_value, value)) def sub_setpoint_meta(timestamp, **kwargs): setpoint_meta_called.append(timestamp) pv.subscribe(sub_setpoint, event_type=pv.SUB_SETPOINT) pv.subscribe(sub_setpoint_meta, event_type=pv.SUB_SETPOINT_META) pv.wait_for_connection() pv.put(1, wait=True) pv.put(2, wait=True) time.sleep(0.5) assert len(setpoint_called) >= 3 assert len(setpoint_meta_called) >= 3
def test_enum_strs(self): epics.PV = FakeEpicsPV sig = EpicsSignal('connects') sig.wait_for_connection() enums = ['enum_strs'] # hack this onto the FakeEpicsPV sig._read_pv.enum_strs = enums self.assertEquals(sig.enum_strs, enums)
def test_epicssignal_waveform(self): epics.PV = FakeEpicsWaveform def update_cb(value=None, **kwargs): self.assertIn(value, FakeEpicsWaveform.strings) signal = EpicsSignal('readpv', string=True) signal.wait_for_connection() signal.subscribe(update_cb) self.assertIn(signal.value, FakeEpicsWaveform.strings)
def test_fakepv_signal(): sig = EpicsSignal(write_pv='XF:31IDA-OP{Tbl-Ax:X1}Mtr.VAL', read_pv='XF:31IDA-OP{Tbl-Ax:X1}Mtr.RBV') st = sig.set(1) for j in range(10): if st.done: break time.sleep(.1) assert st.done
def test_setpoint(self): epics.PV = FakeEpicsPV sig = EpicsSignal('connects') sig.wait_for_connection() sig.get_setpoint() sig.get_setpoint(as_string=True)
def test_epicssignal_get_in_callback(cleanup, fake_motor_ioc): pvs = fake_motor_ioc.pvs sig = EpicsSignal(write_pv=pvs['setpoint'], read_pv=pvs['readback'], name='motor') cleanup.add(sig) called = [] def generic_sub(sub_type, **kwargs): called.append((sub_type, sig.get(), sig.get_setpoint())) for event_type in (sig.SUB_VALUE, sig.SUB_META, sig.SUB_SETPOINT, sig.SUB_SETPOINT_META): sig.subscribe(generic_sub, event_type=event_type) sig.wait_for_connection() sig.put(1, wait=True) sig.put(2, wait=True) time.sleep(0.5) print(called) # Arbitrary threshold, but if @klauer screwed something up again, this will # blow up assert len(called) < 20 print('total', len(called))
def test_epicssignal_waveform(cleanup, signal_test_ioc): def update_cb(value=None, **kwargs): assert len(value) > 1 signal = EpicsSignal(signal_test_ioc.pvs['waveform'], string=True) cleanup.add(signal) signal.wait_for_connection() sub = signal.subscribe(update_cb, event_type=signal.SUB_VALUE) assert len(signal.value) > 1 signal.unsubscribe(sub)
def set_severity_signal(cleanup, signal_test_ioc): sig = EpicsSignal(signal_test_ioc.pvs['set_severity'], name='set_severity') cleanup.add(sig) sig.wait_for_connection() return sig
def rw_signal(cleanup, signal_test_ioc): sig = EpicsSignal(signal_test_ioc.pvs['read_write'], name='read_write') cleanup.add(sig) sig.wait_for_connection() return sig
def bool_enum_signal(cleanup, signal_test_ioc): sig = EpicsSignal(signal_test_ioc.pvs['bool_enum'], name='bool_enum') cleanup.add(sig) sig.wait_for_connection() return sig
def __init__(self, setpoint_PV, readback_PV, name): self.control_PV = EpicsSignal(setpoint_PV) # DG/Vitara PV self.storage_PV = EpicsSignal(readback_PV) # Notepad PV self.name = name
def liveMonitor(sigList=['29idd:tc1:setVal_SP.VAL', '29idd:tc1:getVal_B'], displayList=['TC1 setpoint', 'TC1 readback'], delay=1, points=None, testPlot=False, liveTab=False, ylab=True, verbose=False): ''' liveMonitor plots (with live updates) PVs. Inputs: sigList: List of PVs to plot. Defaults are two signals from 29ID. displayList: Display names for the plots for each PV. Defaults are two signals from 29ID. delay: delay in seconds between data acquisitions. Defaults to 1 second points: number of points to acquire, Defaults to infinite testPlot: boolean set to True for using bluesky test signals (det1, noisy_det). Defaults to False liveTab boolean for adding a LiveTable callback during plan. Defaults to False (no LiveTable). ylab: boolean for using EGU field of 1st PV as y-label of plot. Defaults to True (use EGU field) verbose: boolean for showing tracebacks. Default is false which suppresses traceback listing ''' if not verbose: sys.tracebacklimit = 0 else: sys.tracebacklimit = 1000 #setup PVs to be monitored pvList = [] if not testPlot: for j in range(len(sigList)): sig = EpicsSignal(sigList[j], name=displayList[j]) pvList.append(sig) if j == 0: ylabel = 'EGU' #need to add code to use value of EGU field of #first PV as y-axis label else: pvList = [noisy_det, det1, det2] displayList = ['noisy_det', 'det1', 'det2'] ylabel = 'Arb. units' #create plot object fig, ax = plt.subplots() #Rescaling operation -- needs to be here to get ax object class AutoScaleAxes(object): def resetAxes(self, event): ax.set_xlim(auto=True) ax.set_ylim(auto=True) #Rescale button ASA = AutoScaleAxes() axreset = plt.axes([0.62, 0.91, 0.14, 0.07]) breset = Button(axreset, 'Reset Axes') breset.on_clicked(ASA.resetAxes) #Quit button DFC = DataFlowControl() axquit = plt.axes([0.79, 0.91, 0.18, 0.07]) bquit = Button(axquit, 'Quit Monitor') bquit.on_clicked(DFC.quitMon) lm_callbacks = [] #Define LiveTable callback if liveTab: lm_callbacks.append(LiveTable(pvList)) #Define multi-plot callback for i in pvList: lm_callbacks.append(LivePlot2(i, x='time', ax=ax, label=i.name)) #set axis labels ax.set_xlabel('Time') if ylab: ax.set_ylabel(ylabel) else: ax.set_ylabel(' ') #reformat axis tick labels and data lmformat = mdates.DateFormatter('%m/%d %H:%M:%S') ax.xaxis.set_major_formatter(lmformat) ax.format_xdata = lmformat labels = ax.get_xticklabels() plt.setp(labels, rotation=30, ha='right') plt.subplots_adjust(bottom=0.20) #start Run Engine RE(count(pvList, delay=delay, num=points), lm_callbacks) return
class _OptionalEpicsSignal(Signal): """ An EPICS Signal which may or may not exist. The init parameters mirror those of :class:`~ophyd.EpicsSignal`. Notes ----- This should be considered for internal use only, and not for user-facing device components. If you use this in your new device, there is a good chance we will reject your PR. """ def __init__(self, read_pv, write_pv=None, *, name, parent=None, kind=None, **kwargs): self._saw_connection = False self._epics_signal = EpicsSignal(read_pv=read_pv, write_pv=write_pv, parent=self, name=name, kind=kind, **kwargs) super().__init__(name=name, parent=parent, kind=kind) self._epics_signal.subscribe( self._epics_meta_update, event_type=self._epics_signal.SUB_META, ) def _epics_value_update(self, **kwargs): """The EpicsSignal value updated.""" super().put(value=kwargs['value'], timestamp=kwargs['timestamp'], force=True) # Note: the above internally calls run_subs # self._run_subs(**kwargs) def _epics_meta_update(self, sub_type=None, **kwargs): """The EpicsSignal metadata updated; reflect that here.""" self._metadata.update(**kwargs) self._run_subs(sub_type=self.SUB_META, **kwargs) if not self._saw_connection and kwargs.get('connected', False): self._epics_signal.subscribe(self._epics_value_update) self._saw_connection = True def destroy(self): super().destroy() self._epics_signal.destroy() self._epics_signal = None def should_use_epics_signal(self) -> bool: """ Tell `_OptionalEpicsSignal` whether or not to use the `EpicsSignal`. By default, the `EpicsSignal` will be used if the PV has connected. Note ---- * Subclasses should override this with their own functionality. * This value should not change during the lifetime of the `_OptionalEpicsSignal`. """ return self._saw_connection def _proxy_method(method_name): # noqa """ Proxy a method from either the EpicsSignal or the superclass Signal. """ def method_selector(self, *args, **kwargs): owner = (self._epics_signal if self.should_use_epics_signal() else super()) return getattr(owner, method_name)(*args, **kwargs) return method_selector describe = _proxy_method('describe') describe_configuration = _proxy_method('describe_configuration') get = _proxy_method('get') put = _proxy_method('put') set = _proxy_method('set') read = _proxy_method('read') read_configuration = _proxy_method('read_configuration') wait_for_connection = _proxy_method('wait_for_connection') def _proxy_property(prop_name, value): # noqa """Read-only property proxy for the internal EPICS Signal.""" def getter(self): if self.should_use_epics_signal(): return getattr(self._epics_signal, prop_name) return value # Only support read-only properties for now. return property(getter) connected = _proxy_property('connected', True) read_access = _proxy_property('read_access', True) write_access = _proxy_property('write_access', True) precision = _proxy_property('precision', 4) enum_strs = _proxy_property('enum_strs', ()) limits = _proxy_property('limits', (0, 0)) @property def kind(self): """The EPICS signal's kind.""" return self._epics_signal.kind @kind.setter def kind(self, value): self._epics_signal.kind = value
def test_describe(): sig = EpicsSignal('my_pv') sig._write_pv.enum_strs = ('enum1', 'enum2') sig.wait_for_connection() sig.put(1) desc = sig.describe()['my_pv'] assert desc['dtype'] == 'integer' assert desc['shape'] == [] assert 'precision' in desc assert 'enum_strs' in desc assert 'upper_ctrl_limit' in desc assert 'lower_ctrl_limit' in desc sig.put('foo') desc = sig.describe()['my_pv'] assert desc['dtype'] == 'string' assert desc['shape'] == [] sig.put(3.14) desc = sig.describe()['my_pv'] assert desc['dtype'] == 'number' assert desc['shape'] == [] import numpy as np sig.put(np.array([ 1, ])) desc = sig.describe()['my_pv'] assert desc['dtype'] == 'array' assert desc['shape'] == [ 1, ]
def test_epicssignal_set(cleanup, motor, put_complete): sim_pv = EpicsSignal(write_pv=motor.user_setpoint.pvname, read_pv=motor.user_readback.pvname, put_complete=put_complete) cleanup.add(sim_pv) sim_pv.wait_for_connection() logging.getLogger('ophyd.signal').setLevel(logging.DEBUG) logging.getLogger('ophyd.utils.epics_pvs').setLevel(logging.DEBUG) print('tolerance=', sim_pv.tolerance) assert sim_pv.tolerance is not None start_pos = sim_pv.get() # move to +0.2 and check the status object target = sim_pv.get() + 0.2 st = sim_pv.set(target, timeout=1, settle_time=0.001) wait(st, timeout=5) assert st.done assert st.success print('status 1', st) assert abs(target - sim_pv.get()) < 0.05 # move back to -0.2, forcing a timeout with a low value target = sim_pv.get() - 0.2 st = sim_pv.set(target, timeout=1e-6) time.sleep(0.5) print('status 2', st) assert st.done assert not st.success # keep the axis in position st = sim_pv.set(start_pos) wait(st, timeout=5)
def test_describe(cleanup, signal_test_ioc): sig = EpicsSignal(signal_test_ioc.pvs['bool_enum'], name='my_pv') cleanup.add(sig) sig.wait_for_connection() sig.put(1) desc = sig.describe()['my_pv'] assert desc['dtype'] == 'integer' assert desc['shape'] == [] # assert 'precision' in desc assert desc['enum_strs'] == ['Off', 'On'] assert 'upper_ctrl_limit' in desc assert 'lower_ctrl_limit' in desc sig = Signal(name='my_pv') sig.put('Off') desc = sig.describe()['my_pv'] assert desc['dtype'] == 'string' assert desc['shape'] == [] sig.put(3.14) desc = sig.describe()['my_pv'] assert desc['dtype'] == 'number' assert desc['shape'] == [] import numpy as np sig.put(np.array([ 1, ])) desc = sig.describe()['my_pv'] assert desc['dtype'] == 'array' assert desc['shape'] == [ 1, ]
def test_enum_strs(cleanup, signal_test_ioc): sig = EpicsSignal(signal_test_ioc.pvs['bool_enum']) cleanup.add(sig) sig.wait_for_connection() assert sig.enum_strs == ('Off', 'On')
def test_epicssignal_readwrite(cleanup, signal_test_ioc): signal = EpicsSignal(read_pv=signal_test_ioc.pvs['read_only'], write_pv=signal_test_ioc.pvs['read_write'], limits=True) cleanup.add(signal) signal.wait_for_connection() assert signal.setpoint_pvname == signal_test_ioc.pvs['read_write'] assert signal.pvname == signal_test_ioc.pvs['read_only'] signal.value signal._update_rate = 2 time.sleep(0.2) value = 10 signal.value = value signal.setpoint = value assert signal.setpoint == value signal.setpoint_ts signal.limits signal.precision signal.timestamp signal.read() signal.describe() signal.read_configuration() signal.describe_configuration() eval(repr(signal)) time.sleep(0.2)
def test_epicssignal_readwrite_limits(cleanup, signal_test_ioc): signal = EpicsSignal(read_pv=signal_test_ioc.pvs['read_only'], write_pv=signal_test_ioc.pvs['read_write'], limits=True) cleanup.add(signal) signal.wait_for_connection() signal.check_value((signal.low_limit + signal.high_limit) / 2) with pytest.raises(ValueError): signal.check_value(None) with pytest.raises(ValueError): signal.check_value(signal.low_limit - 1) with pytest.raises(ValueError): signal.check_value(signal.high_limit + 1)
def test_epicssignal_readwrite_limits(): signal = EpicsSignal('readpv', write_pv='readpv', limits=True) signal.wait_for_connection() signal.check_value((signal.low_limit + signal.high_limit) / 2) try: signal.check_value(None) except ValueError: pass else: raise ValueError('value=None') try: signal.check_value(signal.low_limit - 1) except ValueError: pass else: raise ValueError('lower limit %s' % (signal.limits, )) try: signal.check_value(signal.high_limit + 1) except ValueError: pass else: raise ValueError('upper limit')
def test_no_connection(cleanup, signal_test_ioc): sig = EpicsSignal('does_not_connect') cleanup.add(sig) with pytest.raises(TimeoutError): sig.wait_for_connection() sig = EpicsSignal('does_not_connect') cleanup.add(sig) with pytest.raises(TimeoutError): sig.put(0.0) with pytest.raises(TimeoutError): sig.get() sig = EpicsSignal(signal_test_ioc.pvs['read_only'], write_pv='does_not_connect') cleanup.add(sig) with pytest.raises(TimeoutError): sig.wait_for_connection()
def test_setpoint(): sig = EpicsSignal('connects') sig.wait_for_connection() sig.get_setpoint() sig.get_setpoint(as_string=True)
class User(): target_x = Motor('MEC:USR:MMS:17', name='target_x_motor') YFEon = YFEon YFEoff = YFEoff HWPon = HWPon SHG_opt = SHG_opt save_scope_to_eLog = save_scope_to_eLog def start_seq(self, rate=120, wLPLaser=False): if rate == 120: sync_mark = 6 #int(_sync_markers[120]) elif rate == 60: sync_mark = 5 elif rate == 30: sync_mark = 4 elif rate == 10: sync_mark = 3 elif rate == 5: sync_mark = 2 elif rate == 1: sync_mark = 1 elif rate == 0.5: sync_mark = 0 seq.sync_marker.put(sync_mark) seq.play_mode.put(2) # Run sequence forever ff_seq = [[169, 0, 0, 0]] if wLPLaser: ff_seq.append([182, 0, 0, 0]) seq.sequence.put_seq(ff_seq) seq.start() _seq = Sequence() _sync_markers = {0.5: 0, 1: 1, 5: 2, 10: 3, 30: 4, 60: 5, 120: 6, 360: 7} nsl = NanoSecondLaser() fsl = FemtoSecondLaser() shutters = [1, 2, 3, 4, 5, 6] _shutters = { 1: shutter1, 2: shutter2, 3: shutter3, 4: shutter4, 5: shutter5, 6: shutter6 } seq_a_pvs = [ EpicsSignal('MEC:ECS:IOC:01:EC_6:00'), EpicsSignal('MEC:ECS:IOC:01:EC_6:01'), EpicsSignal('MEC:ECS:IOC:01:EC_6:02'), EpicsSignal('MEC:ECS:IOC:01:EC_6:03'), EpicsSignal('MEC:ECS:IOC:01:EC_6:04'), EpicsSignal('MEC:ECS:IOC:01:EC_6:05'), EpicsSignal('MEC:ECS:IOC:01:EC_6:06'), EpicsSignal('MEC:ECS:IOC:01:EC_6:07'), EpicsSignal('MEC:ECS:IOC:01:EC_6:08'), EpicsSignal('MEC:ECS:IOC:01:EC_6:09') ] seq_b_pvs = [ EpicsSignal('MEC:ECS:IOC:01:BD_6:00'), EpicsSignal('MEC:ECS:IOC:01:BD_6:01'), EpicsSignal('MEC:ECS:IOC:01:BD_6:02'), EpicsSignal('MEC:ECS:IOC:01:BD_6:03'), EpicsSignal('MEC:ECS:IOC:01:BD_6:04'), EpicsSignal('MEC:ECS:IOC:01:BD_6:05'), EpicsSignal('MEC:ECS:IOC:01:BD_6:06'), EpicsSignal('MEC:ECS:IOC:01:BD_6:07'), EpicsSignal('MEC:ECS:IOC:01:BD_6:08'), EpicsSignal('MEC:ECS:IOC:01:BD_6:09') ] def open_shutters(self): print("Opening shutters...") for shutter in self.shutters: self._shutters[shutter].open() time.sleep(5) def close_shutters(self): print("Closing shutters...") for shutter in self.shutters: self._shutters[shutter].close() time.sleep(5) def seq_wait(self): time.sleep(0.5) while seq.play_status.get() != 0: time.sleep(0.5) def scalar_sequence_write(self, s): for i in range(len(s)): self.seq_a_pvs[i].put(s[i][0]) for j in range(len(s)): self.seq_b_pvs[j].put(s[j][1]) seq.sequence_length.put(len(s))
def test_epicssignal_readwrite(self): epics.PV = FakeEpicsPV signal = EpicsSignal('readpv', write_pv='writepv') signal.wait_for_connection() self.assertEquals(signal.setpoint_pvname, 'writepv') self.assertEquals(signal.pvname, 'readpv') signal.value signal._update_rate = 2 time.sleep(0.2) value = 10 signal.value = value signal.setpoint = value self.assertEquals(signal.setpoint, value) signal.setpoint_ts signal.limits signal.precision signal.timestamp signal.read() signal.describe() signal.read_configuration() signal.describe_configuration() eval(repr(signal)) time.sleep(0.2)
class TimingChannel(object): def __init__(self, setpoint_PV, readback_PV, name): self.control_PV = EpicsSignal(setpoint_PV) # DG/Vitara PV self.storage_PV = EpicsSignal(readback_PV) # Notepad PV self.name = name def save_t0(self, val=None): """ Set the t0 value directly, or save the current value as t0. """ if not val: # Take current value to be t0 if not provided val = self.control_PV.get() self.storage_PV.put(val) # Save t0 value def restore_t0(self): """ Restore the t0 value from the current saved value for t0. """ val = self.storage_PV.get() self.control_PV.put(val) # write t0 value def mvr(self, relval): """ Move the control PV relative to it's current value. """ currval = self.control_PV.get() self.control_PV.put(currval - relval) def mv(self, val): t0 = self.storage_PV.get() self.control_PV.put(t0 - val) def get_delay(self, verbose=False): delay = self.control_PV.get() - self.storage_PV.get() if delay > 0: print("X-rays arrive {} s before the optical laser".format( abs(delay))) elif delay < 0: print("X-rays arrive {} s after the optical laser".format( abs(delay))) else: # delay is 0 print("X-rays arrive at the same time as the optical laser") if verbose: control_data = (self.name, self.control_PV.pvname, self.control_PV.get()) storage_data = (self.name, self.storage_PV.pvname, self.storage_PV.get()) print("{} Control PV: {}, Control Value: {}".format(*control_data)) print("{} Storage PV: {}, Storage Value: {}".format(*storage_data))
class FuncPositioner(FltMvInterface, SoftPositioner): """ Class for hacking together a positioner-like object. Before you use this, make sure your use case cannot be easily handled by a `PVPositioner` or by a `PseudoPositioner`. This should be fast to set up, but has the following limitations: - Inspection tools will not work properly, in general - typhos, happi, and similar tools will not work - It is not possible to know ahead of time if any control layer signals poked by this device are connected. - Metadata about the control layer will not be provided. - It's not possible to intelligently subscribe to control layer signals. Everything will be periodic polls on background threads. - Session performance may be negatively impacted by slow polling functions. Parameters ---------- name : str, keyword-only The name to use when we refer to this positioner. move : func, keyword-only The function to call to cause the device to move. The signature must be def fn(position) get_pos : func, keyword-only The function to call to check the device's position. The signature must be def fn() set_pos : func, optional, keyword-only If provided, the function to call to change the device's shown position without moving it. The signature must be def fn(position) stop : func, optional, keyword-only If provided, the function to call to stop the motor. The signature must be def fn() done : func, optional, keyword-only If provided, the function to call to check if the device is done moving. The signature must be def fn() -> bool, and it must return True when motion is complete and false otherwise. check_value : func, optional, keyword-only If provided, an extra function to call to check if it is safe to move. The signature must be def fn(position) -> raise ValueError info : func, optional, keyword-only If provided, an extra function to add data to the ipython console pretty-print. The signature must be def fn() -> dict{str: value}. egu : str, optional If provided, the metadata units for the positioner. limits : tuple of floats, optional If provided, we'll raise an exception to reject moves outside of this range. update_rate : float, optional How often to update the position and check for move completion during a move. Defaults to 1 second. timeout : float, optional How long to wait before marking an in-progress move as failed. Defaults to 60 seconds. notepad_pv : str, optional If provided, a PV to put to whenever we move. This can be used to allow other elements in the control system to see what this positioner is doing. """ def __init__(self, *, name, move, get_pos, set_pos=None, stop=None, done=None, check_value=None, info=None, egu='', limits=None, update_rate=1, timeout=60, notepad_pv=None, parent=None, kind=None, **kwargs): self._check_signature('move', move, 1) self._move = move self._check_signature('get_pos', get_pos, 0) self._get_pos = get_pos self._check_signature('set_pos', set_pos, 1) self._set_pos = set_pos self._check_signature('stop', stop, 0) self._stop = stop self._check_signature('done', done, 0) self._done = done self._check_signature('check_value', check_value, 1) self._check = check_value self._info = info self._last_update = 0 self._goal = None self.update_rate = 1 notepad_name = name + '_notepad' if notepad_pv is None: self.notepad_signal = Signal(name=notepad_name) else: self.notepad_signal = EpicsSignal(notepad_pv, name=notepad_name) if parent is None and kind is None: kind = 'hinted' super().__init__(name=name, egu=egu, limits=limits, source='func', timeout=timeout, parent=parent, kind=kind, **kwargs) def _check_signature(self, name, func, nargs): if func is None: return sig = inspect.signature(func) try: sig.bind(*(0, ) * nargs) except TypeError: raise ValueError( f'FuncPositioner recieved {name} with an incorrect. ' f'signature. Must be able to take {nargs} args.') from None def _setup_move(self, position, status): self._run_subs(sub_type=self.SUB_START, timestamp=time.time()) self._goal = position self._started_moving = True self._moving = True self._finished = False self._move(position) self._new_update(status) def _new_update(self, status): schedule_task(self._update_task, args=(status, ), delay=self.update_rate) def _update_task(self, status): self._update_position() if self._check_finished(): try: status.set_finished() except InvalidState: pass if status.done: self._started_moving = False self._moving = False else: self._new_update(status) @property def position(self): self._update_position() return self._position def _update_position(self): if time.monotonic() - self._last_update > self.update_rate: self._last_update = time.monotonic() pos = self._get_pos() self._set_position(pos) self.notepad_signal.put(pos) def _check_finished(self): if self._done is None: finished = np.isclose(self._goal, self.position) else: finished = self._done() if finished: self._done_moving() return finished def set_position(self, position): if self._set_pos is None: raise NotImplementedError(f'FuncPositioner {self.name} was not ' 'given a set_pos argument.') else: self._set_pos(position) def stop(self, *args, **kwargs): if self._stop is None: # Often called by bluesky, don't throw an exception self.log.warning(f'Called stop on FuncPositioner {self.name}, ' 'but was not given a stop argument.') else: self._stop() super().stop(*args, **kwargs) def check_value(self, pos): super().check_value(pos) if self._check is not None: self._check(pos) def status_info(self): info = super().status_info() if self._info is not None: info.update(self._info()) return info
class User(): target_x = Motor('MEC:USR:MMS:17', name='target_x_motor') YFEon = YFEon YFEoff = YFEoff HWPon = HWPon SHG_opt = SHG_opt save_scope_to_eLog = save_scope_to_eLog def start_seq(self, rate=120, wLPLaser=False): if rate==120: sync_mark = 6#int(_sync_markers[120]) elif rate==60: sync_mark = 5 elif rate==30: sync_mark = 4 elif rate==10: sync_mark = 3 elif rate==5: sync_mark = 2 elif rate==1: sync_mark = 1 elif rate==0.5: sync_mark = 0 seq.sync_marker.put(sync_mark) seq.play_mode.put(2) # Run sequence forever ff_seq = [[169, 0, 0, 0]] if wLPLaser: ff_seq.append([182, 0, 0, 0]) seq.sequence.put_seq(ff_seq) seq.start() def start_seq_120Hz(self): #self.start_seq_120Hz(120) sync_mark = 6#int(_sync_markers[120]) seq.sync_marker.put(sync_mark) seq.play_mode.put(2) # Run sequence forever ff_seq = [[169, 0, 0, 0]] seq.sequence.put_seq(ff_seq) seq.start() def start_seq_10Hz(self, wLPLaser=False): sync_mark = 3#int(_sync_markers[10]) seq.sync_marker.put(sync_mark) seq.play_mode.put(2) # Run sequence forever ff_seq = [[169, 0, 0, 0]] if wLPLaser: ff_seq.append([182, 0, 0, 0]) seq.sequence.put_seq(ff_seq) seq.start() _seq = Sequence() _sync_markers = {0.5:0, 1:1, 5:2, 10:3, 30:4, 60:5, 120:6, 360:7} nsl = NanoSecondLaser() fsl = FemtoSecondLaser() shutters = [1,2,3,4,5,6] _shutters = {1: shutter1, 2: shutter2, 3: shutter3, 4: shutter4, 5: shutter5, 6: shutter6} seq_a_pvs = [EpicsSignal('MEC:ECS:IOC:01:EC_6:00'), EpicsSignal('MEC:ECS:IOC:01:EC_6:01'), EpicsSignal('MEC:ECS:IOC:01:EC_6:02'), EpicsSignal('MEC:ECS:IOC:01:EC_6:03'), EpicsSignal('MEC:ECS:IOC:01:EC_6:04'), EpicsSignal('MEC:ECS:IOC:01:EC_6:05'), EpicsSignal('MEC:ECS:IOC:01:EC_6:06'), EpicsSignal('MEC:ECS:IOC:01:EC_6:07'), EpicsSignal('MEC:ECS:IOC:01:EC_6:08'), EpicsSignal('MEC:ECS:IOC:01:EC_6:09')] seq_b_pvs = [EpicsSignal('MEC:ECS:IOC:01:BD_6:00'), EpicsSignal('MEC:ECS:IOC:01:BD_6:01'), EpicsSignal('MEC:ECS:IOC:01:BD_6:02'), EpicsSignal('MEC:ECS:IOC:01:BD_6:03'), EpicsSignal('MEC:ECS:IOC:01:BD_6:04'), EpicsSignal('MEC:ECS:IOC:01:BD_6:05'), EpicsSignal('MEC:ECS:IOC:01:BD_6:06'), EpicsSignal('MEC:ECS:IOC:01:BD_6:07'), EpicsSignal('MEC:ECS:IOC:01:BD_6:08'), EpicsSignal('MEC:ECS:IOC:01:BD_6:09')] def open_shutters(self): print("Opening shutters...") for shutter in self.shutters: self._shutters[shutter].open() time.sleep(5) def close_shutters(self): print("Closing shutters...") for shutter in self.shutters: self._shutters[shutter].close() time.sleep(5) def seq_wait(self): time.sleep(0.5) while seq.play_status.get() != 0: time.sleep(0.5) def scalar_sequence_write(self, s): for i in range(len(s)): self.seq_a_pvs[i].put(s[i][0]) for j in range(len(s)): self.seq_b_pvs[j].put(s[j][1]) seq.sequence_length.put(len(s)) def uxi_shot(self, delta=0.3, record=True, lasps=True): """ Returns a BlueSky plan to run a scan for the LV08 experiment. Used for the UXI camera which requires near continuous acquisition for stable camera behavior. The following shots are combined in a single run. Shot sequence: -------------- 1) 10 dark frames # Warm up camera 2) 10 X-ray frames 3) Sample moves in 4) 10 dark frames # Warm up camera 5) 1 X-ray + Optical laser frame 6) 10 dark frames 7) Sample moves out 8) 10 dark frames # Warm up camera 9) 10 X-ray frames Parameters: ----------- delta : float <default: 0.3> The relative distance in mm to move the sample in and out. record : bool <default: True> Flag to record the data (or not). lasps : bool <default: True> Flag to perform pre-and post shot pulse shaping routines. """ logging.debug("Calling User.shot with parameters:") logging.debug("delta: {}".format(delta)) logging.debug("record: {}".format(record)) logging.debug("lasps: {}".format(lasps)) print("Configuring DAQ...") # yield from bps.configure(daq,events=0, record=record) # run infinitely, let sequencer # control number of events daq.begin_infinite(record=record) long_seq = [[0, 240, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] print("Configuring sequencer...") # Setup the pulse picker for single shots in flip flop mode pp.flipflop(wait=True) # Setup sequencer for requested rate sync_mark = int(self._sync_markers[0.5]) seq.sync_marker.put(sync_mark) seq.play_mode.put(1) # Run N times seq.rep_count.put(10) # Setup sequence self._seq.rate = 0.5 # close the shutters specified by the user for shutter in self.shutters: self._shutters[shutter].close() # Shutters are slow; give them time to close time.sleep(5) if lasps: print("Running mecps.pspreshot()...") pspreshot() # Run 10 Pre-laser dark shots (step 1 above) s = self._seq.darkSequence(1, preshot=False) # seq.sequence.put_seq(long_seq) # seq.sequence.put_seq(s) self.scalar_sequence_write(s) print("Taking 10 dark shots...") time.sleep(1) seq.start() self.seq_wait() # yield from bps.trigger_and_read([daq, seq]) # Run 10 Pre-laser x-ray shots (step 2 above) s = self._seq.darkXraySequence(1, preshot=False) # seq.sequence.put_seq(long_seq) # seq.sequence.put_seq(s) self.scalar_sequence_write(s) print("Taking 10 x-ray shots...") time.sleep(1) seq.start() self.seq_wait() #yield from bps.trigger_and_read([daq, seq]) # Move sample in (step 3 above) print("Moving sample in...") yield from bps.mvr(self.target_x, delta) #TODO Check direction # Run 10 Pre-laser dark shots (step 4 above) print("Taking 10 dark shots...") s = self._seq.darkSequence(1, preshot=False) # seq.sequence.put_seq(long_seq) # seq.sequence.put_seq(s) self.scalar_sequence_write(s) time.sleep(1) seq.start() self.seq_wait() # yield from bps.trigger_and_read([daq, seq]) # Run x-ray + optical sequence (step 5 above) print("Taking optical laser shots...") seq.rep_count.put(1) s = self._seq.duringSequence(1, 'longpulse') # seq.sequence.put_seq(long_seq) # seq.sequence.put_seq(s) self.scalar_sequence_write(s) time.sleep(1) seq.start() self.seq_wait() # yield from bps.trigger_and_read([daq, seq]) # Run 10 Post-laser dark shots (step 6 above) print("Taking 10 dark shots...") seq.rep_count.put(10) s = self._seq.darkSequence(1, preshot=False) # seq.sequence.put_seq(long_seq) # seq.sequence.put_seq(s) self.scalar_sequence_write(s) time.sleep(1) seq.start() self.seq_wait() # yield from bps.trigger_and_read([daq, seq]) # Move sample out (step 7 above) print("Moving sample out...") yield from bps.mvr(self.target_x, -delta) #TODO Check direction # Run 10 Pre-x-ray dark shots (step 8 above) print("Taking 10 dark shots...") seq.rep_count.put(10) s = self._seq.darkSequence(1, preshot=False) # seq.sequence.put_seq(long_seq) # seq.sequence.put_seq(s) self.scalar_sequence_write(s) time.sleep(1) seq.start() self.seq_wait() # yield from bps.trigger_and_read([daq, seq]) # Run 10 Pre-laser x-ray shots (step 9 above) print("Taking 10 x-ray shots...") s = self._seq.darkXraySequence(1, preshot=False) # seq.sequence.put_seq(long_seq) # seq.sequence.put_seq(s) self.scalar_sequence_write(s) time.sleep(1) seq.start() self.seq_wait() # yield from bps.trigger_and_read([daq, seq]) daq.end_run() if lasps: print("Running mecps.pspostshot()...") pspostshot() # open the shutters specified by the user for shutter in self.shutters: self._shutters[shutter].open()
def motor_pair_signal(cleanup, motor): sig = EpicsSignal(write_pv=motor.user_setpoint.pvname, read_pv=motor.user_readback.pvname) cleanup.add(sig) sig.wait_for_connection() return sig
def test_epicssignal_set(put_complete): sim_pv = EpicsSignal(write_pv='XF:31IDA-OP{Tbl-Ax:X1}Mtr.VAL', read_pv='XF:31IDA-OP{Tbl-Ax:X1}Mtr.RBV', put_complete=put_complete) sim_pv.wait_for_connection() logging.getLogger('ophyd.signal').setLevel(logging.DEBUG) logging.getLogger('ophyd.utils.epics_pvs').setLevel(logging.DEBUG) print('tolerance=', sim_pv.tolerance) assert sim_pv.tolerance is not None start_pos = sim_pv.get() # move to +0.2 and check the status object target = sim_pv.get() + 0.2 st = sim_pv.set(target, timeout=1, settle_time=0.001) wait(st) assert st.done assert st.success print('status 1', st) assert abs(target - sim_pv.get()) < 0.05 # move back to -0.2, forcing a timeout with a low value target = sim_pv.get() - 0.2 st = sim_pv.set(target, timeout=1e-6) time.sleep(0.1) print('status 2', st) assert st.done assert not st.success # keep the axis in position st = sim_pv.set(start_pos) wait(st)
''' Start the Ramping process on all channels ''' self.start_ramp.put(1) def stop(self): ''' Stops the Ramping process on all channels ''' self.stop_ramp.put(1) def is_ramping(self): ''' Returns wether the power supply is ramping or not ''' return (int(self.unit_status.get()) >> 30) == 1 def is_interlock_ok(self): ''' Returns the interlock state ''' st = int(self.unit_status.get()) return (st & 1) & ((st >> 1) & 1) == 1 def is_on(self): ''' Returns wether the Channels are ON or OFF ''' return (int(self.unit_status.get()) >> 29) == 1 def wait(self): while self.is_ramping(): sleep(0.1) hfm_bimorph = Bimorph('XF:17IDA-OP:FMX{Mir:HFM-PS}', name='hfm_bimorph') kb_bimorph = Bimorph('XF:17IDC-OP:FMX{Mir:KB-PS}', name='kb_bimorph') vkb_piezo_tweak = EpicsSignal('XF:17IDC-BI:FMX{Best:2}:PreDAC0:OutCh1') hkb_piezo_tweak = EpicsSignal('XF:17IDC-BI:FMX{Best:2}:PreDAC0:OutCh2')
def alarm_status_signal(cleanup, signal_test_ioc): sig = EpicsSignal(signal_test_ioc.pvs['alarm_status'], name='alarm_status') cleanup.add(sig) sig.wait_for_connection() return sig
def test_epicssignal_readwrite(): signal = EpicsSignal('readpv', write_pv='writepv') signal.wait_for_connection() assert signal.setpoint_pvname == 'writepv' assert signal.pvname == 'readpv' signal.value signal._update_rate = 2 time.sleep(0.2) value = 10 signal.value = value signal.setpoint = value assert signal.setpoint == value signal.setpoint_ts signal.limits signal.precision signal.timestamp signal.read() signal.describe() signal.read_configuration() signal.describe_configuration() eval(repr(signal)) time.sleep(0.2)
class User(): def __init__(self): #switch to trigger instead of full evr self._sync_markers = {0.5:0, 1:1, 5:2, 10:3, 30:4, 60:5, 120:6, 360:7} self.trigger_mag = Trigger('XCS:R42:EVR:01:TRIG4', name='trigger_mag') self.trigger_pp = Trigger('XCS:USR:EVR:TRIG1', name='trigger_pp') self.nemptyshots = actions(0, name='nemptyshots') self.isfire = actions(False, name='isfire') self.emptyshotspacing = actions(0, name='emptyshotspacing') self.nshots = actions(0, name='nshots') self.shotspacing = actions(0, name='shotspacing') #with safe_load('Pirani and Cold Cathode Gauges'): # self.mag_pirani = BaseGauge('XCS:USR:GPI:01', name='mag_pirani') # self.mag_cc = BaseGauge('XCS:USR:GCC:01', name='mag_cc') self.sample_temp = LakeShoreChannel('XCS:USR:TCT:02', name='A') self.mag_temp = LakeShoreChannel('XCS:USR:TCT:02', name='B') self.ttl_high = 2.0 self.ttl_low = 0.8 self._ready_sig = EpicsSignal('XCS:USR:ai1:0', name='User Analog Input channel 0', kind = 'omitted') self._ipm4_threshold = IPM4_Threshold(0) #default threshold is 0 self.ipm4_pv = EpicsSignal('XCS:SB1:BMMON:SUM', name='ipm4_sum') self.ipm4_mag_retry = 10 self._gdet_threshold_pv = EpicsSignal('XCS:VARS:J78:GDET_THRES', kind = 'normal') self.gdet_avg_count = 30 self.gdet_mag_retry = 10 self.gdet = GasDetector('GDET:FEE1', name='gas detector') #self._bykik_pv = Cpt(EpicsSignal('IOC:IN20:EV01:BYKIK_ABTACT', kind = 'normal', string=True, doc='BYKIK: Abort Active') self._req_burst_rate = 'Full' self._test_burst_rate = EpicsSignal('PATT:SYS0:1:TESTBURSTRATE', name='test_burst_rate', kind='normal') self._mps_burst_rate = EpicsSignal('PATT:SYS0:1:MPSBURSTRATE', name='mps_burst_rate') # number of seconds to pause between empty and magnet self.pause_time = 2. self._min_empty_delay = 4 self._beam_owner = EpicsSignal('ECS:SYS0:0:BEAM_OWNER_ID', name='beam_owner', kind = 'normal') self._att = att self.hutch = 'xcs' self._hutch_id = EpicsSignal('ECS:SYS0:4:HUTCH_ID', name='hutch_id', kind = 'normal') self.aliases = ['BEAM'] self.gainmodes = '' #use lcls.(tab) to find bykik controls @property def machine_burst_rate(self): if self._beam_owner.get() == self._hutch_id.get(): self._mps_burst_rate.get() else: self._test_burst_rate.get() @machine_burst_rate.setter def machine_burst_rate(self, rate): if self._beam_owner.get() == self._hutch_id.get(): self._mps_burst_rate.put(rate) else: self._test_burst_rate.put(rate) def check_burst_rate(self): if self.machine_burst_rate != self._req_burst_rate: print('Machine burst frequency not set to %s! - fixing...' %self._req_burst_rate) if self._beam_owner.get() == self._hutch_id.get(): set_and_wait(self._mps_burst_rate, self._req_burst_rate) #ophyd set and wait() - ophyd.utils.epics_pvs.set_and_wait else: set_and_wait(self._test_burst_rate, self._req_burst_rate) print('done.') @property def ready_sig(self): self._ready_sig.get() # def prepare_burst(self, nShots=None, nWaitShots=None): # sync_mark = int(self._sync_markers[120]) # seq.sync_marker.put(sync_mark) # seq.play_mode.put(0) # ff_seq = [[84,0,0,0]] # ff_seq = ff_seq.append([86, 2, 0, 0]) # if nShots is not None: # if isinstance(nShots, int): # ff_seq.append([84, nShots-2, 0, 0]) # else: # ff_seq.append([84, int(nShots*120)-2, 0, 0]) # if isinstance(nWaitShots, int): # ff_seq.append([86, nWaitShots-2, 0, 0]) # seq.sequence.put_seq(ff_seq) def _prepare_burst(self, Nshots, Nbursts=1, freq=120, delay=None, burstMode=False): if Nshots == 1: pp.flipflop() elif Nshots > 1: pp.burst() beamrate = lcls.beam_event_rate.get() if Nbursts == 1: seq.play_mode.put(0) elif Nbursts > 1: seq.play_mode.put(1) seq.rep_count.put(Nbursts) elif Nbursts < 0: seq.play_mode.put(2) if burstMode: burst = Nshots + 2 else: burst = 0 if not freq == 120: raise(NotImplementedError('Not implemented yet!')) if Nshots == 1: if delay is None: delay = 3 elif delay < 2: raise ValueError('Delay between flip flops of less than two is not allowed') ff_seq = [[84,delay,0,burst], [86,0,0,burst], [85,2,0,burst]] elif Nshots > 1: if delay is None: delay = 1 elif delay < 1: raise ValueError('Delay between bursts of less than one is not allowed') ff_seq = [ [84,delay,0,burst], [86,0,0,burst], [86,1,0,burst] ] for shotNo in range(Nshots): ff_seq.append([85, 1, 0, burst]) if shotNo==Nshots-2: ff_seq.append([84, 0, 0, burst]) seq.sequence.put_seq(ff_seq) def _prepNonMagShots(self, nshots): #copy code from old pulsepicker.py file in (blinst) and change where needed or xpp experiment self._prepare_burst(nshots, burstMode=True) def _prepSpacedNonMagShots(self, nshots_per_burst, spacing): nshots = 1 burstDelay = spacing - 2 self.prepare_burst(nshots, Nbursts=nshots_per_burst, delay=burstDelay, burstMode=True) def _takeNonMagShots(self): seq.start() def _prepMagShot(self, isfire, delay=1): playcount = int(seq.play_count.get()) pp.flipflop() seq.play_mode.put(0) ff_seq = [ [84,delay,0,3], [86,0,0,0] ] if isfire: ff_seq.append([self.trigger_mag.get_eventcode(), 2, 0, 0]) ff_seq.append([85, 0, 0, 0]) else: ff_seq.append([85, 2, 0, 0]) seq.sequence.put_seq(ff_seq) playcount += 1 seq.play_count.put(playcount) def _takeMagShot(self): seq.start() def takeEmptyShots(self, nshots, shotspacing, use_daq=False, record=None): self.nshots.value = nshots self.shotspacing.value = shotspacing calibcontrols=[ (self.nshots), (self.shotspacing) ] if shotspacing > 0: self._prepSpacedNonMagShots(self.nshots.value, self.shotspacing.value) else: self._prepNonMagShots(self.nshots.value) #configure daq if being used if use_daq: daq.record = record daq.configure(events=0, controls=calibcontrols) try: if use_daq: daq.begin(event=nshots,controls=calibcontrols) seq.start() seq.pause() if use_daq: daq.wait() except KeyboardInterrupt: seq.stop() finally: if use_daq: daq.record = None daq.disconnect() def takeMagnetShot(self, nemptyshots, emptyshotspacing=0, isfire=False, record=None): self.isfire.value = isfire self.nemptyshots.value = nemptyshots self.emptyshotspacing.value=emptyshotspacing calibcontrols=[ (self.isfire), (self.trigger_mag), (self.trigger_mag), (self.trigger_mag), (self.nemptyshots), (self.emptyshotspacing) ] # check the machine burst rate and set to Full rate if not self.check_burst_rate() # disable BYKIK before taking shots lcls.bykik_disable() # check if emptyshotspacing is valid if 0 < self.emptyshotspacing.value < self._min_empty_delay: raise ValueError("When using spacing between empty shots it must be >= %d" %self._min_empty_delay) spacer = "*********************************" print("\n%s\n* Preparing to take magnet shot *\n%s\n" %(spacer, spacer)) if emptyshotspacing > 0: mag_status = "Taking %d shots, with a spacing between each of %d beam pulses, before firing the magnet\n" %(self.nemptyshots.value, self.emptyshotspacing.value) else: mag_status = "Taking %d shots before firing the magnet\n" %self.nemptyshots.value mag_status += "Magnet pulse eventcode: %d\n" %self.trigger_mag.get_eventcode() mag_status += "Magnet pulse trigger delay: %f\n" %self.trigger_mag.get_delay() mag_status += "Magnet pulse trigger width: %f\n" %self.trigger_mag.get_width() mag_status += "Magnet to be fired: %s\n" %self.isfire.value print(mag_status) try: daq.record = record daq.configure(events=0, controls=calibcontrols) # Pre empty shots if self.nemptyshots.value > 0: print("\nPreparing sequencer for pre-firing, non-magnet shots") if self.emptyshotspacing.value > 0: self._prepSpacedNonMagShots(self.nemptyshots.value, self.emptyshotspacing.value) else: self._prepNonMagShots(self.nemptyshots.value) daq.begin(events=self.nemptyshots.value,controls=calibcontrols) print("\nTaking %d pre-firing, non-magnet shots\n" %self.nemptyshots.value) # pause after changing pulse picker mode time.sleep(self.pause_time) self._takeNonMagShots() seq.pause() daq.wait() else: print("\nSkipping prefiring, non-magnet shots\n") # pause after empty shots time.sleep(self.pause_time) # Fire the magnet sequence based on isfire flag if isfire: print("Start magnet firing sequence\n") else: print("Start magnet test 'firing' sequence\n") print("\nPreparing sequencer for magnet shot") if self.emptyshotspacing.value > 0: self._prepMagShot(self.isfire.value, self.emptyshotspacing.value) else: self._prepMagShot(self.isfire.value) # pause after changing pulse picker mode time.sleep(self.pause_time) #checking ipm4?? num_tries = 0 ipm4_good = False while num_tries < self.ipm4_mag_retry: if self.ipm4_pv < self._ipm4_threshold: print("\nNot firing magnet due to low beam current (ipm4): %.3f \n" %(self.ipm4_pv.get())) backoff_time = 2 ** num_tries print("Sleeping for %d seconds...\n" %backoff_time) time.sleep(backoff_time) num_tries += 1 else: #ipm4 is good - fire! ipm4_good = True print("\nIPM4 looks good (ipm4): %.3f\n" %self.ipm4_pv.get()) break if not ipm4_good: print("Max number of ipm4 checks (%d) exceeded! - Abort shot attempt.\n" %self.ipm4_mag_retry) return False # take the magnet shot daq.begin(events=1,controls=calibcontrols) print("\nTaking magnet shot\n") self._takeMagShot() seq.pause() daq.wait() #pause after magnet shots time.sleep(self.pause_time) #post empty shots if nemptyshots > 0: print("\nPreparing sequencer for post-firing, non-magnet shots") if self.emptyshotspacing.value > 0: self._prepSpacedNonMagShots(self.nemptyshots.value, self.emptyshotspacing.value) else: self._prepNonMagShots(self.nemptyshots.value) daq.begin(events=self.nemptyshots.value,controls=calibcontrols) print("\nTaking %d post-firing, non-magnet shots\n" %self.nemptyshots.value) # pause after changing pulse picker mode time.sleep(self.pause_time) self._takeNonMagShots() seq.pause() daq.wait() else: print("\nSkipping post-firing, non-magnet shots\n") return True except KeyboardInterrupt: seq.stop() daq.stop() return False finally: daq.record = None daq.disconnect() lcls.bykik_enable() def takeMagnetShotMulti(self, nemptyshots, emptyshotspacing=0, isfire=False, record=None, ignore_ready=False): """ Takes magnet shots in a continous fashion waiting for a ready signal from the magnet controller """ self.nemptyshots.value = nemptyshots self.emptyshotspacing.value = emptyshotspacing self.isfire.value = isfire latch = False nmagshots = 0 shotgood = True spacer = '##############################' try: while shotgood: if not latch and (self.ready_sig > self.ttl_high or ignore_ready): if self.ipm4_pv < self._ipm4_threshold: print("\nNot firing magnet due to low beam current (ipm4): %.3f \n" %(self.ipm4_pv.get())) backoff_time = 1 print("Sleeping for %d second...\n" %backoff_time) time.sleep(backoff_time) continue latch = True print("\n%s\nStarting shot %d\n%s\n" %(spacer,nmagshots,spacer)) start_time = time.time() shotgood = self.takeMagnetShot(self.nemptyshots.value, self.emptyshotspacing.value, self.isfire.value, record) stop_time = time.time() print("\n%s\nCompleted shot %d in %.2f s\n%s\n" %(spacer,nmagshots,(stop_time-start_time),spacer)) nmagshots += 1 if latch and (self.ready_sig < self.ttl_low or ignore_ready): latch = False time.sleep(0.25) except KeyboardInterrupt: print('\nExiting...\n') finally: print('Took %d total magnet shots\n' %nmagshots) def takeRun(self, nEvents, record=None, use_l3t=False): daq.configure(events=120, record=record, use_l3t=use_l3t) daq.begin(events=nEvents) daq.wait() daq.end_run() def ascan(self, motor, start, end, nsteps, nEvents, record=None, use_l3t=False): self.cleanup_RE() currPos = motor.wm() daq.configure(nEvents, record=record, controls=[motor], use_l3t=use_l3t) try: RE(scan([daq], motor, start, end, nsteps)) except Exception: logger.debug('RE Exit', exc_info=True) finally: self.cleanup_RE() motor.mv(currPos) def pv_ascan(self, signal, start, end, nsteps, nEvents, record=None, use_l3t=False): self.cleanup_RE() currPos = signal.get() daq.configure(nEvents, record=record, controls=[signal], use_l3t=use_l3t) try: RE(scan([daq], signal, start, end, nsteps)) except Exception: logger.debug('RE Exit', exc_info=True) finally: self.cleanup_RE() signal.put(currPos) def listscan(self, motor, posList, nEvents, record=None, use_l3t=False): self.cleanup_RE() currPos = motor.wm() daq.configure(nEvents, record=record, controls=[motor], use_l3t=use_l3t) try: RE(list_scan([daq], motor, posList)) except Exception: logger.debug('RE Exit', exc_info=True) finally: self.cleanup_RE() motor.mv(currPos) def list3scan(self, m1, p1, m2, p2, m3, p3, nEvents, record=None, use_l3t=False): self.cleanup_RE() currPos1 = m1.wm() currPos2 = m2.wm() currPos3 = m3.wm() daq.configure(nEvents, record=record, controls=[m1,m2,m3], use_l3t=use_l3t) try: RE(list_scan([daq], m1,p1,m2,p2,m3,p3)) except Exception: logger.debug('RE Exit', exc_info=True) finally: self.cleanup_RE() m1.mv(currPos1) m2.mv(currPos2) m3.mv(currPos3) def dscan(self, motor, start, end, nsteps, nEvents, record=None, use_l3t=False): self.cleanup_RE() daq.configure(nEvents, record=record, controls=[motor], use_l3t=use_l3t) currPos = motor.wm() try: RE(scan([daq], motor, currPos+start, currPos+end, nsteps)) except Exception: logger.debug('RE Exit', exc_info=True) finally: self.cleanup_RE() motor.mv(currPos) def pv_dscan(self, signal, start, end, nsteps, nEvents, record=None, use_l3t=False): self.cleanup_RE() daq.configure(nEvents, record=record, controls=[signal], use_l3t=use_l3t) currPos = signal.get() try: RE(scan([daq], signal, currPos+start, currPos+end, nsteps)) except Exception: logger.debug('RE Exit', exc_info=True) finally: self.cleanup_RE() signal.put(currPos) def a2scan(self, m1, a1, b1, m2, a2, b2, nsteps, nEvents, record=True, use_l3t=False): self.cleanup_RE() daq.configure(nEvents, record=record, controls=[m1, m2], use_l3t=use_l3t) try: RE(scan([daq], m1, a1, b1, m2, a2, b2, nsteps)) except Exception: logger.debug('RE Exit', exc_info=True) finally: self.cleanup_RE() def a3scan(self, m1, a1, b1, m2, a2, b2, m3, a3, b3, nsteps, nEvents, record=True): self.cleanup_RE() daq.configure(nEvents, record=record, controls=[m1, m2, m3]) try: RE(scan([daq], m1, a1, b1, m2, a2, b2, m3, a3, b3, nsteps)) except Exception: logger.debug('RE Exit', exc_info=True) finally: self.cleanup_RE() def delay_scan(self, start, end, sweep_time, record=None, use_l3t=False, duration=None): """Delay scan with the daq.""" self.cleanup_RE() daq.configure(events=None, duration=None, record=record, use_l3t=use_l3t, controls=[lxt_fast]) try: RE(delay_scan(daq, lxt_fast, [start, end], sweep_time, duration=duration)) except Exception: logger.debug('RE Exit', exc_info=True) finally: self.cleanup_RE() def empty_delay_scan(self, start, end, sweep_time, record=None, use_l3t=False, duration=None): """Delay scan without the daq.""" self.cleanup_RE() #daq.configure(events=None, duration=None, record=record, # use_l3t=use_l3t, controls=[lxt_fast]) try: RE(delay_scan(None, lxt_fast, [start, end], sweep_time, duration=duration)) except Exception: logger.debug('RE Exit', exc_info=True) finally: self.cleanup_RE() def cleanup_RE(self): if not RE.state.is_idle: print('Cleaning up RunEngine') print('Stopping previous run') try: RE.stop() except Exception: pass
def __init__(self): #switch to trigger instead of full evr self._sync_markers = {0.5:0, 1:1, 5:2, 10:3, 30:4, 60:5, 120:6, 360:7} self.trigger_mag = Trigger('XCS:R42:EVR:01:TRIG4', name='trigger_mag') self.trigger_pp = Trigger('XCS:USR:EVR:TRIG1', name='trigger_pp') self.nemptyshots = actions(0, name='nemptyshots') self.isfire = actions(False, name='isfire') self.emptyshotspacing = actions(0, name='emptyshotspacing') self.nshots = actions(0, name='nshots') self.shotspacing = actions(0, name='shotspacing') #with safe_load('Pirani and Cold Cathode Gauges'): # self.mag_pirani = BaseGauge('XCS:USR:GPI:01', name='mag_pirani') # self.mag_cc = BaseGauge('XCS:USR:GCC:01', name='mag_cc') self.sample_temp = LakeShoreChannel('XCS:USR:TCT:02', name='A') self.mag_temp = LakeShoreChannel('XCS:USR:TCT:02', name='B') self.ttl_high = 2.0 self.ttl_low = 0.8 self._ready_sig = EpicsSignal('XCS:USR:ai1:0', name='User Analog Input channel 0', kind = 'omitted') self._ipm4_threshold = IPM4_Threshold(0) #default threshold is 0 self.ipm4_pv = EpicsSignal('XCS:SB1:BMMON:SUM', name='ipm4_sum') self.ipm4_mag_retry = 10 self._gdet_threshold_pv = EpicsSignal('XCS:VARS:J78:GDET_THRES', kind = 'normal') self.gdet_avg_count = 30 self.gdet_mag_retry = 10 self.gdet = GasDetector('GDET:FEE1', name='gas detector') #self._bykik_pv = Cpt(EpicsSignal('IOC:IN20:EV01:BYKIK_ABTACT', kind = 'normal', string=True, doc='BYKIK: Abort Active') self._req_burst_rate = 'Full' self._test_burst_rate = EpicsSignal('PATT:SYS0:1:TESTBURSTRATE', name='test_burst_rate', kind='normal') self._mps_burst_rate = EpicsSignal('PATT:SYS0:1:MPSBURSTRATE', name='mps_burst_rate') # number of seconds to pause between empty and magnet self.pause_time = 2. self._min_empty_delay = 4 self._beam_owner = EpicsSignal('ECS:SYS0:0:BEAM_OWNER_ID', name='beam_owner', kind = 'normal') self._att = att self.hutch = 'xcs' self._hutch_id = EpicsSignal('ECS:SYS0:4:HUTCH_ID', name='hutch_id', kind = 'normal') self.aliases = ['BEAM'] self.gainmodes = ''
def __init__(self, door_state, voltage_read, voltage_write, voltage_scale): self.door_state = EpicsSignalRO(door_state) self.mesh_voltage = EpicsSignal(voltage_read, write_pv=voltage_write) self.voltage_scale = EpicsSignal(voltage_scale) print('Using door pv {}'.format(door_state))
def test_describe(): sig = EpicsSignal('my_pv') sig._write_pv.enum_strs = ('enum1', 'enum2') sig.wait_for_connection() sig.put(1) desc = sig.describe()['my_pv'] assert desc['dtype'] == 'integer' assert desc['shape'] == [] assert 'precision' in desc assert 'enum_strs' in desc assert 'upper_ctrl_limit' in desc assert 'lower_ctrl_limit' in desc sig.put('foo') desc = sig.describe()['my_pv'] assert desc['dtype'] == 'string' assert desc['shape'] == [] sig.put(3.14) desc = sig.describe()['my_pv'] assert desc['dtype'] == 'number' assert desc['shape'] == [] import numpy as np sig.put(np.array([1,])) desc = sig.describe()['my_pv'] assert desc['dtype'] == 'array' assert desc['shape'] == [1,]
def test_describe(self): epics.PV = FakeEpicsPV sig = EpicsSignal('my_pv') sig._write_pv.enum_strs = ('enum1', 'enum2') sig.wait_for_connection() sig.put(1) desc = sig.describe()['my_pv'] self.assertEquals(desc['dtype'], 'integer') self.assertEquals(desc['shape'], []) self.assertIn('precision', desc) self.assertIn('enum_strs', desc) self.assertIn('upper_ctrl_limit', desc) self.assertIn('lower_ctrl_limit', desc) sig.put('foo') desc = sig.describe()['my_pv'] self.assertEquals(desc['dtype'], 'string') self.assertEquals(desc['shape'], []) sig.put(3.14) desc = sig.describe()['my_pv'] self.assertEquals(desc['dtype'], 'number') self.assertEquals(desc['shape'], []) import numpy as np sig.put(np.array([1,])) desc = sig.describe()['my_pv'] self.assertEquals(desc['dtype'], 'array') self.assertEquals(desc['shape'], [1,])
class User(): target_x = Motor('MEC:USR:MMS:17', name='target_x_motor') YFEon = YFEon YFEoff = YFEoff HWPon = HWPon SHG_opt = SHG_opt save_scope_to_eLog = save_scope_to_eLog def start_seq(self, rate=120, wLPLaser=False): if rate == 120: sync_mark = 6 #int(_sync_markers[120]) elif rate == 60: sync_mark = 5 elif rate == 30: sync_mark = 4 elif rate == 10: sync_mark = 3 elif rate == 5: sync_mark = 2 elif rate == 1: sync_mark = 1 elif rate == 0.5: sync_mark = 0 seq.sync_marker.put(sync_mark) seq.play_mode.put(2) # Run sequence forever ff_seq = [[169, 0, 0, 0]] if wLPLaser: ff_seq.append([182, 0, 0, 0]) seq.sequence.put_seq(ff_seq) seq.start() _seq = Sequence() _sync_markers = {0.5: 0, 1: 1, 5: 2, 10: 3, 30: 4, 60: 5, 120: 6, 360: 7} nsl = NanoSecondLaser() fsl = FemtoSecondLaser() shutters = [1, 2, 3, 4, 5, 6] _shutters = { 1: shutter1, 2: shutter2, 3: shutter3, 4: shutter4, 5: shutter5, 6: shutter6 } seq_a_pvs = [ EpicsSignal('MEC:ECS:IOC:01:EC_6:00'), EpicsSignal('MEC:ECS:IOC:01:EC_6:01'), EpicsSignal('MEC:ECS:IOC:01:EC_6:02'), EpicsSignal('MEC:ECS:IOC:01:EC_6:03'), EpicsSignal('MEC:ECS:IOC:01:EC_6:04'), EpicsSignal('MEC:ECS:IOC:01:EC_6:05'), EpicsSignal('MEC:ECS:IOC:01:EC_6:06'), EpicsSignal('MEC:ECS:IOC:01:EC_6:07'), EpicsSignal('MEC:ECS:IOC:01:EC_6:08'), EpicsSignal('MEC:ECS:IOC:01:EC_6:09') ] seq_b_pvs = [ EpicsSignal('MEC:ECS:IOC:01:BD_6:00'), EpicsSignal('MEC:ECS:IOC:01:BD_6:01'), EpicsSignal('MEC:ECS:IOC:01:BD_6:02'), EpicsSignal('MEC:ECS:IOC:01:BD_6:03'), EpicsSignal('MEC:ECS:IOC:01:BD_6:04'), EpicsSignal('MEC:ECS:IOC:01:BD_6:05'), EpicsSignal('MEC:ECS:IOC:01:BD_6:06'), EpicsSignal('MEC:ECS:IOC:01:BD_6:07'), EpicsSignal('MEC:ECS:IOC:01:BD_6:08'), EpicsSignal('MEC:ECS:IOC:01:BD_6:09') ] def open_shutters(self): print("Opening shutters...") for shutter in self.shutters: self._shutters[shutter].open() time.sleep(5) def close_shutters(self): print("Closing shutters...") for shutter in self.shutters: self._shutters[shutter].close() time.sleep(5) def seq_wait(self): time.sleep(0.5) while seq.play_status.get() != 0: time.sleep(0.5) def scalar_sequence_write(self, s): for i in range(len(s)): self.seq_a_pvs[i].put(s[i][0]) for j in range(len(s)): self.seq_b_pvs[j].put(s[j][1]) seq.sequence_length.put(len(s)) tx_motor = target.x ty_motor = target.y grid = XYTargetGrid(x=tx_motor, y=ty_motor, x_init=0.0, y_init=0.0, x_spacing=-0.363, y_spacing=0.363, x_comp=0.01, y_comp=0.01, name='lv25_targetgrid') def x_scan(self, nshots=1, record=True, xrays=False, carriage_return=False): """ Returns a BlueSky plan to perform a scan in X. Collects a number of short pulse laser shots while moving from target to target. Parameters: ----------- nshots : int <default: 1> The number of shots that you would like to take in the run. record : bool <default: True> Flag to record the data (or not). xrays : bool <default: False> Flag to do an optical or x-ray shot. If False, does an optical only shot. If True, you do a optical + x-ray shot. carriage_return : bool <default: False> Flag to return to initial position. """ logging.debug("Calling User.x_scan with parameters:") logging.debug("nshots: {}".format(nshots)) logging.debug("record: {}".format(record)) logging.debug("xrays: {}".format(xrays)) print("Configuring DAQ...") daq.configure(events=0, record=record, begin_sleep=0.1) # run infinitely # daq.configure(events=nshots, record=record) # daq.begin_infinite() print("Configuring sequencer...") # Setup the pulse picker for single shots in flip flop mode pp.flipflop(wait=True) # Setup sequencer for requested rate sync_mark = int(self._sync_markers[5]) seq.sync_marker.put(sync_mark) seq.play_mode.put(0) # Run once # Setup sequence self._seq.rate = 5 if xrays: s = self._seq.duringSequence(1, 'shortpulse') else: s = self._seq.opticalSequence(1, 'shortpulse') seq.sequence.put_seq(s) self._shutters[6].close() time.sleep(5) # Get starting positions start = self.grid.wm() yield from scan([daq, seq], self.grid.x, start['x'], (start['x'] + self.grid.x_spacing * (nshots - 1)), num=nshots) # for i in range(nshots): # if i is not 0: # yield from bps.mv(start['x']+self.grid.x_spacing*i) ## yield from bps.trigger_and_read([daq,seq]) ## yield from bps.trigger(seq, wait=True) # yield from count([daq,seq], num=1) if carriage_return: # Return to start print("Returning to starting position") yield from bps.mv(self.grid.x, start['x']) yield from bps.mv(self.grid.y, start['y']) daq.end_run() self._shutters[6].open() def _x_position(self, xstart, xspacing, ix, iy, dxx=0.0, dxy=0.0): """ Determine the appropriate X position of a motor on a grid, given regular, measured deviations from the ideal grid due to mounting, etc. If deviations are omitted, then this will simply return the ideal grid position given the defined spacing. Parameters ---------- xstart : float Initial x position (e.g. x position of target "zero"). xspacing : float Ideal spacing for grid. ix : int Zero-indexed target x-index. iy : int Zero-indexed target y-index. dxx : float (default=0.0) Deviation in x position per x-index from ideal. dxy : float (deafult=0.0) Deviation in x position per y-index from ideal. """ return xstart + (xspacing + dxx) * ix + dxy * iy def _y_position(self, ystart, yspacing, ix, iy, dyy=0.0, dyx=0.0): """ Determine the appropriate Y position of a motor on a grid, given regular, measured deviations from the ideal grid due to mounting, etc. If deviations are omitted, then this will simply return the ideal grid position given the defined spacing. Parameters ---------- ystart : float Initial y position (e.g. y position of target "zero"). yspacing : float Ideal spacing for grid. ix : int Zero-indexed target x-index. iy : int Zero-indexed target y-index. dyy : float (default=0.0) Deviation in y position per y-index from ideal. dyx : float (deafult=0.0) Deviation in y position per x-index from ideal. """ return ystart + (yspacing + dyy) * iy + dyx * ix def _list_scan_positions(self, xstart, xspacing, ystart, yspacing, nx, ny, dxx=0.0, dxy=0.0, dyy=0.0, dyx=0.0): """ Return lists of x and y positions for use in a BlueSky list_scan plan. Calculates x and y positions for a sample grid given any non-idealities in the grid alignment. Parameters ---------- xstart : float Initial x position (e.g. x position of target "zero"). xspacing : float Ideal spacing for grid. ystart : float Initial y position (e.g. y position of target "zero"). yspacing : float Ideal spacing for grid. nx : int The number of x positions in the grid. ny : int The number of y positions in the grid. dxx : float (default=0.0) Deviation in x position per x-index from ideal. dxy : float (deafult=0.0) Deviation in x position per y-index from ideal. dyy : float (default=0.0) Deviation in y position per y-index from ideal. dyx : float (deafult=0.0) Deviation in y position per x-index from ideal. """ xl = [] yl = [] for i in range(ny): for j in range(nx): xl.append( self._x_position(xstart, xspacing, j, i, dxx=dxx, dxy=dxy)) yl.append( self._y_position(ystart, yspacing, j, i, dyy=dyy, dyx=dyx)) return xl, yl def xy_scan(self, nxshots=1, nyshots=1, record=True, xrays=False, carriage_return=False): """ Returns a BlueSky plan to perform a scan in X and Y. Collects a number of short pulse laser shots while moving from target to target. Parameters: ----------- nxshots : int <default: 1> The number of shots that you would like to take on the x axis for each "line" on the target stage. nyshots : int <default: 1> The number of lines that you want to move on the y axis. record : bool <default: True> Flag to record the data (or not). xrays : bool <default: False> Flag to do an optical or x-ray shot. If false, does an optical only shot. If true, you do a optical + x-ray shot. carriage_return : bool <default: False> Flag to return to initial position. """ logging.debug("Calling User.xy_scan with parameters:") logging.debug("nxshots: {}".format(nxshots)) logging.debug("nyshots: {}".format(nyshots)) logging.debug("record: {}".format(record)) logging.debug("xrays: {}".format(xrays)) print("Configuring DAQ...") daq.configure(events=0, record=record) # run infinitely # daq.begin_infinite() print("Configuring sequencer...") # Setup the pulse picker for single shots in flip flop mode pp.flipflop(wait=True) # Setup sequencer for requested rate sync_mark = int(self._sync_markers[5]) seq.sync_marker.put(sync_mark) seq.play_mode.put(0) # Run once # Setup sequence self._seq.rate = 5 if xrays: s = self._seq.duringSequence(1, 'shortpulse') else: s = self._seq.opticalSequence(1, 'shortpulse') seq.sequence.put_seq(s) # Get starting positions start = self.grid.wm() # Get lists of scan positions xl, yl = self._list_scan_positions(start['x'], self.grid.x_spacing, start['y'], self.grid.y_spacing, nxshots, nyshots, dxx=0.0, dxy=self.grid.x_comp, dyy=0.0, dyx=self.grid.y_comp) # Close shutter 6 (requested) self._shutters[6].close() time.sleep(5) # Scan the thing def inner(): yield from list_scan([daq, seq], self.grid.y, yl, self.grid.x, xl) yield from inner() if carriage_return: # Return to start print("Returning to starting position") yield from bps.mv(self.grid.x, start['x']) yield from bps.mv(self.grid.y, start['y']) daq.end_run() self._shutters[6].open() def xy_fly_scan(self, nshots, nrows=2, y_distance=None, rate=5, record=True, xrays=True): """ Plan for doing a 2D fly scan. Uses the target x motor as the flying axis, running for a specified distance at a specified velocity, taking shots at a specified rate. Parameters ---------- nshots : int The number of shots to take in the x scan. rate : int <default : 5> The rate at which to take shots (120, 30, 10, 5, 1) y_distance : float <default : x.grid.y_spacing> The distance to move the y stage down. nrows : int <default : 2> The number of "rows" to scan the x stage on. record : bool <default : True> Flag to record the data. xrays : bool <default : True> Flag to take an x-ray + optical (True) shot or optical only (False). """ logging.debug("rate: {}".format(rate)) logging.debug("nshots: {}".format(nshots)) logging.debug("nrows: {}".format(nrows)) logging.debug("record: {}".format(record)) logging.debug("xrays: {}".format(xrays)) if not y_distance: y_distance = self.grid.y_spacing logging.debug("y_distance: {}".format(y_distance)) assert rate in [120, 30, 10, 5, 1], "Please choose a rate in {120, 30, 10,5,1}" print("Configuring DAQ...") daq.configure(events=0, record=record) # run infinitely daq.begin_infinite() print("Configuring sequencer...") # Setup the pulse picker for single shots in flip flop mode pp.flipflop(wait=True) # Setup sequencer for requested rate sync_mark = int(self._sync_markers[rate]) seq.sync_marker.put(sync_mark) seq.play_mode.put(1) # Run for n shots seq.rep_count.put(nshots) # Setup sequence self._seq.rate = rate if xrays: s = self._seq.duringSequence(1, 'shortpulse') else: s = self._seq.opticalSequence(1, 'shortpulse') seq.sequence.put_seq(s) # Get starting positions start = self.grid.wm() # Calculate and set velocity vel = self.grid.x_spacing * rate # mm * (1/s) = mm/s self.grid.x.velocity.put(vel) # Estimate distance to move given requested shots and rate dist = (nshots / rate) * vel # (shots/(shots/sec))*mm/s = mm # Close shutter 6 (requested) self._shutters[6].close() time.sleep(5) for i in range(nrows): if i != 0: yield from bps.mvr(self.grid.y, y_distance) # Play the sequencer seq.play_control.put(1) # Start the move yield from bps.mvr(self.grid.x, dist) # Waits for move to complete # Make sure the sequencer stopped seq.play_control.put(0) yield from bps.mv(self.grid.x, start['x']) # Return to start print("Returning to starting position") yield from bps.mv(self.grid.x, start['x']) yield from bps.mv(self.grid.y, start['y']) daq.end_run() self._shutters[6].open()