示例#1
0
class Part:
    def __init__(self, part):
        # Let errors propagate
        self._name = part.get('name')
        self._vset = part.get('vset')
        self._base = part.get('base')
        self._scale = part.get('scale')
        self._ramping = EpicsSignalRO(f'{self._base}{RAMPING}')
        self._set_voltage = EpicsSignal(f'{self._base}{VSET}')

    @property
    def name(self):
        return self._name

    @property
    def vset(self):
        return self._vset

    @property
    def base(self):
        return self._base

    def set_voltage(self, zero=False):
        if zero:
            self._set_voltage.put(0)
        else:
            self._set_voltage.put(self.vset)

    @property
    def ramping(self):
        return self._ramping.get()
示例#2
0
class HxnScanStatus:
    '''Broadcast if a scan is in progress via PV

    Processed on every start/end document
    '''
    def __init__(self, running_pv):
        self._last_start = None
        self.running_signal = EpicsSignal(running_pv)
        self._running = False

    @property
    def running(self):
        return self._running

    @running.setter
    def running(self, running):
        self._running = running

        if running is None:
            running = 0

        try:
            self.running_signal.put(running)
        except DisconnectedError:
            logger.error('ScanRunning PV disconnected. Is the hxntools IOC running?')

    def __call__(self, name, doc):
        '''Bluesky callback with document info'''
        if name == 'start':
            self._last_start = doc
        if self._last_start and name in ('start', 'stop'):
            self.running = (name == 'start')
def stop_turbo():
    turbo_onoff = EpicsSignal('XF:12IDC-VA:2{Det:300KW-TMP:1}OnOff',
                              name='turbo_onoff')
    turbo_onoff.put(0)

    iv1 = EpicsSignal('XF:12IDC-VA:2{Det:300KW-IV:1}Cmd:Cls-Cmd', name='iv1')
    iv1.put(1)
class SolenoidValve:
    """ this works for both gate valves and solenoid valves
        status of the valve is given by PV [deviceName]Pos-Sts
        open the valve by setting PV [deviceName]Cmd:Opn-Cmd to 1
        close the valve by setting PV [deviceName]Cmd:Cls-Cmd to 1
    """
    def __init__(self, devName):
        """ for soft pump valves devName should be a list
            some valves have 
        """
        if isinstance(devName, list):  # soft pump valves
            if len(devName) != 2:
                raise Exception(
                    "2 device names must be given for a soft pump valve.")
            self.has_soft = True
            self.soft_valve = SolenoidValve(devName[1])
            devName = devName[0]
            self.devName = devName
        else:
            self.has_soft = False
            self.devName = devName

        self.sig_status = EpicsSignal(devName + 'Pos-Sts')
        self.sig_open = EpicsSignal(devName + 'Cmd:Opn-Cmd')
        self.sig_close = EpicsSignal(devName + 'Cmd:Cls-Cmd')

    def open(self, softOpen=False):
        print("request to open valve: ", self.devName)
        if sim_on:
            print("simulation mode on, the valve state will not change.")
            return
        if softOpen and self.has_soft:
            self.close()
            self.soft_valve.open()
        else:
            if self.has_soft:
                self.soft_valve.close()
            self.sig_open.put(1)
            check_sig_status(self.sig_status, 1)

    def close(self):
        print("request to close valve: ", self.devName)
        if sim_on:
            return
        if self.has_soft:
            if self.soft_valve.status == 1:
                self.soft_valve.close()
        if self.status == 1:
            self.sig_close.put(1)
            check_sig_status(self.sig_status, 0)

    @property
    def status(self):
        if self.has_soft:
            return 0.5 * self.soft_valve.status + self.sig_status.get()
        else:
            return self.sig_status.get()
示例#5
0
def test_write_pv_timestamp_no_monitor(motor):
    sp = EpicsSignal(motor.user_setpoint.pvname, name='test')

    sp_value0 = sp.get()
    ts0 = sp.timestamp
    sp.put(sp_value0 + 0.1, wait=True)
    time.sleep(0.1)

    sp_value1 = sp.get()
    ts1 = sp.timestamp
    assert ts1 > ts0
    assert_almost_equal(sp_value0 + 0.1, sp_value1)

    sp.put(sp.value - 0.1, wait=True)
示例#6
0
def test_read_pv_timestamp_no_monitor(motor):
    sp = EpicsSignal(motor.user_setpoint.pvname, name='test')
    rbv = EpicsSignalRO(motor.user_readback.pvname, name='test')

    rbv_value0 = rbv.get()
    ts0 = rbv.timestamp
    sp.put(sp.value + 0.1, wait=True)
    time.sleep(0.1)

    rbv_value1 = rbv.get()
    ts1 = rbv.timestamp
    assert ts1 > ts0
    assert_almost_equal(rbv_value0 + 0.1, rbv_value1)

    sp.put(sp.value - 0.1, wait=True)
示例#7
0
def gv_temp_open(sysdev, duration):
    """
    gv_temp_open("XF:21IDB-VA{BT:10-GV:10_D_1}", 1.5 (seconds))
    """
    open_pv_str = sysdev + "Cmd:Opn-Cmd"
    close_pv_str = sysdev + "Cmd:Cls-Cmd"
    open_cmd = EpicsSignal(name="open_cmd",
                           read_pv=open_pv_str,
                           write_pv=open_pv_str)
    close_cmd = EpicsSignal(name="close_cmd",
                            read_pv=close_pv_str,
                            write_pv=close_pv_str)
    open_cmd.put(1)
    time.sleep(duration)
    close_cmd.put(1)
示例#8
0
    def test_write_pv_timestamp_monitor(self):
        mtr = EpicsMotor(config.motor_recs[0])
        mtr.wait_for_connection()

        sp = EpicsSignal(mtr.user_setpoint.pvname, auto_monitor=True)

        sp_value0 = sp.get()
        ts0 = sp.timestamp
        sp.put(sp_value0 + 0.1, wait=True)
        time.sleep(0.1)

        sp_value1 = sp.get()
        ts1 = sp.timestamp
        self.assertGreater(ts1, ts0)
        self.assertAlmostEqual(sp_value0 + 0.1, sp_value1)

        sp.put(sp.value - 0.1, wait=True)
示例#9
0
    def test_write_pv_timestamp_no_monitor(self):
        mtr = EpicsMotor(config.motor_recs[0], name='test')
        mtr.wait_for_connection()

        sp = EpicsSignal(mtr.user_setpoint.pvname, name='test')

        sp_value0 = sp.get()
        ts0 = sp.timestamp
        sp.put(sp_value0 + 0.1, wait=True)
        time.sleep(0.1)

        sp_value1 = sp.get()
        ts1 = sp.timestamp
        self.assertGreater(ts1, ts0)
        self.assertAlmostEqual(sp_value0 + 0.1, sp_value1)

        sp.put(sp.value - 0.1, wait=True)
示例#10
0
    def test_read_pv_timestamp_monitor(self):
        mtr = EpicsMotor(config.motor_recs[0])
        mtr.wait_for_connection()

        sp = EpicsSignal(mtr.user_setpoint.pvname, auto_monitor=True)
        rbv = EpicsSignalRO(mtr.user_readback.pvname, auto_monitor=True)

        rbv_value0 = rbv.get()
        ts0 = rbv.timestamp
        sp.put(sp.value + 0.1, wait=True)
        time.sleep(0.1)

        rbv_value1 = rbv.get()
        ts1 = rbv.timestamp
        self.assertGreater(ts1, ts0)
        self.assertAlmostEqual(rbv_value0 + 0.1, rbv_value1)

        sp.put(sp.value - 0.1, wait=True)
示例#11
0
    def test_read_pv_timestamp_no_monitor(self):
        mtr = EpicsMotor(config.motor_recs[0])
        mtr.wait_for_connection()

        sp = EpicsSignal(mtr.user_setpoint.pvname)
        rbv = EpicsSignalRO(mtr.user_readback.pvname)

        rbv_value0 = rbv.get()
        ts0 = rbv.timestamp
        sp.put(sp.value + 0.1, wait=True)
        time.sleep(0.1)

        rbv_value1 = rbv.get()
        ts1 = rbv.timestamp
        self.assertGreater(ts1, ts0)
        self.assertAlmostEqual(rbv_value0 + 0.1, rbv_value1)

        sp.put(sp.value - 0.1, wait=True)
class MKSGauge:
    """ can read pressure, status of the gauge "OFF", "NO GAUGE"
        turn on and off
    """
    def __init__(self, devName):
        self.pres_sig = EpicsSignal(devName + 'P:Raw-I')
        self.power_ctrl = EpicsSignal(devName + 'Pwr-Cmd')

    def pressure(self):
        """ raise exception if the gause if not present or off
        """
        Pr = self.pres_sig.get()
        if Pr == "NO_GAUGE" or Pr == "OFF":
            raise Exception(Pr, self.pres_sig.name)
        elif Pr == "LO<E-03":
            P0 = 1.0e-3
        elif Pr == ">1.0E+03":
            P0 = 1.1e3
        else:
            P0 = np.float(Pr)

        return P0

    def power(self, state, max_try=10):
        """ turn the gauge on or off
        """
        if max_try <= 0:
            raise Exception("could not change gauge state to ", state)
        self.power_on = self.power_ctrl.get()
        if state == "ON" or state == "on":
            if self.power_on:
                return
            else:
                self.power_ctrl.put(1)
                time.sleep(1.5)
                return self.power(state, max_try - 1)
        else:
            if self.power_on:
                self.power_ctrl.put(0)
                time.sleep(1.5)
                return self.power(state, max_try - 1)
            else:
                return
示例#13
0
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))
示例#14
0
class User:
    """Generic User Object"""
    def __init__(self):
        self.sc1_mesh_raw = EpicsSignal(name='sc1_mesh_raw',
                                        read_pv='CXI:SC1:AIO:04:RAW:ANALOGIN',
                                        write_pv='CXI:SC1:AIO:04:ANALOGOUT')
        self.sc1_mesh_scale = EpicsSignal(name='sc1_mesh_scale',
                                          read_pv='CXI:SC1:AIO:04:SCALE')

    def get_sc1_mesh_voltage(self):
        """
        Get the current power supply voltage
        """
        return self.sc1_mesh_raw.get()

    def set_sc1_mesh_voltage(self, sigIn, wait=True, do_print=True):
        """
        Set voltage on power supply to an absolute value

        Parameters
        ----------
        sigIn: int or float
            Power supply voltage in Volts
        """
        if do_print:
            print('Setting voltage...')
        sigInScaled = sigIn / self.sc1_mesh_scale.get()  # in V
        self.sc1_mesh_raw.put(sigInScaled)
        if wait:
            time.sleep(2.5)
        finalVolt = self.sc1_mesh_raw.get()
        finalVoltSupply = finalVolt * self.sc1_mesh_scale.get()
        if do_print:
            print('Power Supply Setpoint: %s V' % sigIn)
            print('Power Supply Voltage: %s V' % finalVoltSupply)

    def set_sc1_rel_mesh_voltage(self, deltaVolt, wait=True, do_print=True):
        """
        Increase/decrease power supply voltage by a specified amount

        Parameters
        ----------
        deltaVolt: int or float
            Amount to increase/decrease voltage (in Volts) from
            its current value. Use positive value to increase
            and negative value to decrease
        """
        if do_print:
            print('Setting voltage...')
        curr_set = self.sc1_mesh_raw.get_setpoint()
        curr_set_supply = curr_set * self.sc1_mesh_scale.get()
        if do_print:
            print('Previous Power Supply Setpoint: %s V' % curr_set_supply)
        new_voltage = round(curr_set_supply + deltaVolt)
        self.set_sc1_mesh_voltage(new_voltage, wait=wait, do_print=do_print)

    def tweak_mesh_voltage(self, deltaVolt):
        """
        Continuously Increase/decrease power supply voltage by
        specifed amount using arrow keys

        Parameters
        ----------
        deltaVolt: int or float
            Amount to change voltage (in Volts) from its current value at
            each step. After calling with specified step size, use arrow keys
            to keep changing
        ^C:
            exits tweak mode
        """
        print('Use arrow keys (left, right) to step voltage (-, +)')
        while True:
            key = key_press.get_input()
            if key in ('q', None):
                return
            elif key == key_press.arrow_right:
                self.set_sc1_rel_mesh_voltage(deltaVolt,
                                              wait=False,
                                              do_print=False)
            elif key == key_press.arrow_left:
                self.set_sc1_rel_mesh_voltage(-deltaVolt,
                                              wait=False,
                                              do_print=False)
示例#15
0
class PilatusGrabber():
    '''Crude tool for grabbing images from the Pilatus.  Largely following
    the standard BlueSky AreaDetector interface, but monkey-patching
    functionality for the bits that Bruce is too dim to figure out.

    Define the Pilatus Detector
       pilatus = MyDetector('XF:06BMB-ES{Det:PIL100k}:', name='Pilatus')

    Make an PilatusGrabber opbject
       pil = PilatusGrabber(pilatus)

    Take an exposure
       pil.snap()

    Show the image (and maybe copy it elsewhere)
       pil.fetch()

    Properties:
       path:      AreaDetector's file path (cannot have spaces)
       fname:     file name
       template:  substitution template for constructing the resolved file name
       fullname:  AreaDetector's fully resolved file name
       number:    file extension (auto increments)
       threshold: detector energy threshold in keV
       time:      exposure time, sets the exposure time and acquire time
       ready:     flag with a simple check to see if camera is ready to take a picture

    '''
    def __init__(self, source):
        self.source = source
        self.image = EpicsSignal(self.source.prefix + 'image1:ArrayData',
                                 name=self.source.name + ' image')
        self._path = EpicsSignal(self.source.prefix + 'cam1:FilePath')
        self._fname = EpicsSignal(self.source.prefix + 'cam1:FileName')
        self._number = EpicsSignal(self.source.prefix + 'cam1:FileNumber')
        self._template = EpicsSignal(self.source.prefix + 'cam1:FileTemplate')
        self._threshold = EpicsSignal(self.source.prefix +
                                      'cam1:ThresholdEnergy')
        self._fullname = EpicsSignalRO(self.source.prefix +
                                       'cam1:FullFileName_RBV')
        self._statusmsg = EpicsSignalRO(self.source.prefix +
                                        'cam1:StatusMessage_RBV')
        self._busy = EpicsSignalRO(self.source.prefix + 'cam1:AcquireBusy')

    @property
    def path(self):
        return (''.join(
            [chr(x) for x in self._path.value[self._path.value.nonzero()]]))

    @path.setter
    def path(self, path):
        a = numpy.pad(array([ord(x) for x in path]), (0, 256 - len(path)),
                      mode='constant')
        self._path.put(a)

    @property
    def fname(self):
        return (''.join(
            [chr(x) for x in self._fname.value[self._fname.value.nonzero()]]))

    @fname.setter
    def fname(self, fname):
        a = numpy.pad(array([ord(x) for x in fname]), (0, 256 - len(fname)),
                      mode='constant')
        self._fname.put(a)

    @property
    def template(self):
        return (''.join([
            chr(x)
            for x in self._template.value[self._template.value.nonzero()]
        ]))

    @template.setter
    def template(self, template):
        a = numpy.pad(array([ord(x) for x in template]),
                      (0, 256 - len(template)),
                      mode='constant')
        self._template.put(a)

    @property
    def fullname(self):
        return (''.join([
            chr(x)
            for x in self._fullname.value[self._fullname.value.nonzero()]
        ]))

    @property
    def statusmsg(self):
        return (''.join([
            chr(x)
            for x in self._statusmsg.value[self._statusmsg.value.nonzero()]
        ]))

    @property
    def number(self):
        return (self._number.value)

    @number.setter
    def number(self, number):
        self._number.put(number)

    @property
    def threshold(self):
        return (self._threshold.value)

    @threshold.setter
    def threshold(self, threshold):
        self._threshold.put(threshold)

    @property
    def time(self):
        return (self.source.cam.acquire_time.value)

    @time.setter
    def time(self, exposure_time):
        self.source.cam.acquire_time.put(exposure_time)
        self.source.cam.acquire_period.put(exposure_time + 0.004)

    @property
    def numimages(self):
        return (self.source.cam.num_images.value)

    @time.setter
    def numimages(self, numimages):
        self.source.cam.num_images.put(numimages)

    @property
    def ready(self):
        if self._busy.value == 1:
            return False
        elif 'Error' in self.statusmsg:
            return False
        else:
            return True

    def snap(self):
        self.source.stage()
        st = self.source.trigger()
        while not st.done:
            time.sleep(.1)
        ret = self.source.read()
        desc = self.source.describe()
        self.source.unstage()

    def fetch(self, fname='/home/xf06bm/test.tif'):
        array = self.image.get()
        size = (self.source.image.height.value, self.source.image.width.value)
        img = np.reshape(array, size).astype('float')

        #Image.fromarray(img).save(fname, "TIFF")
        ## symlink to file on /nist ???  copy???

        fig, ax = plt.subplots(1)  # Create figure and axes
        rotated_img = ndimage.rotate(img, 90)
        plt.imshow(
            rotated_img, cmap='bone'
        )  ## https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html

        imfile = os.path.basename(self.fullname)
        plt.title(imfile)
        plt.colorbar()
        plt.clim(0, array.max())
        plt.show()
示例#16
0
class MirrorScreen(Display):
    def __init__(self, parent=None, args=None, macros=None):
        super(MirrorScreen, self).__init__(parent=parent, args=args, macros=macros)
        if macros != None:
            self.x_stop = EpicsSignal(macros['XAXIS'] + ".STOP")
            self.y_stop = EpicsSignal(macros['YAXIS'] + ".STOP")
            self.p_stop = EpicsSignal(macros['PITCH'] + ".STOP")

        self.alarm_box_x = QHBoxLayout()
        self.alarm_box_y = QHBoxLayout()
        self.alarm_box_p = QHBoxLayout()
        self.alarm_box_gantry_x = QHBoxLayout()
        self.alarm_box_gantry_y = QHBoxLayout()
        self.ghost = QHBoxLayout()

        self.alarm_x = TyphosAlarmCircle()
        self.alarm_x.channel = "ca://" + macros['XAXIS']
        self.alarm_x.setMaximumHeight(35)
        self.alarm_x.setMaximumWidth(35)

        self.alarm_y = TyphosAlarmCircle()
        self.alarm_y.channel = "ca://" + macros['YAXIS']
        self.alarm_y.setMaximumHeight(35)
        self.alarm_y.setMaximumWidth(35)

        self.alarm_p = TyphosAlarmCircle()
        self.alarm_p.channel = "ca://" + macros['PITCH']
        self.alarm_p.setMaximumHeight(35)
        self.alarm_p.setMaximumWidth(35)

        label_font = QFont()
        label_font.setBold(True) 


        self.x_label = QLabel("x")
        #self.x_label.setFont(label_font)
        self.y_label = QLabel("y")
        #self.y_label.setFont(label_font)
        self.p_label = QLabel("pitch")
        #self.p_label.setFont(label_font)
        self.gantry_x_label = QLabel("gantry x")
        #self.gantry_x_label.setFont(label_font)
        self.gantry_y_label = QLabel("gantry y")
        #self.gantry_y_label.setFont(label_font)

        self.alarm_gantry_x = TyphosAlarmCircle()
        self.alarm_gantry_x.channel = "ca://" + macros['MIRROR'] + ":HOMS:ALREADY_COUPLED_X_RBV"
        self.alarm_gantry_x.setMaximumHeight(35)
        self.alarm_gantry_x.setMaximumWidth(35)

        self.alarm_gantry_y = TyphosAlarmCircle()
        self.alarm_gantry_y.channel = "ca://" + macros['MIRROR'] + ":HOMS:ALREADY_COUPLED_Y_RBV"
        self.alarm_gantry_y.setMaximumHeight(35)
        self.alarm_gantry_y.setMaximumWidth(35)




        
        self.alarm_box_x.addWidget(self.x_label)
        self.alarm_box_x.setAlignment(self.x_label, Qt.AlignCenter)
        self.alarm_box_x.addWidget(self.alarm_x)

        self.alarm_box_y.addWidget(self.y_label)
        self.alarm_box_y.setAlignment(self.y_label, Qt.AlignCenter)
        self.alarm_box_y.addWidget(self.alarm_y)

        self.alarm_box_p.addWidget(self.p_label)
        self.alarm_box_p.setAlignment(self.p_label, Qt.AlignCenter)
        self.alarm_box_p.addWidget(self.alarm_p)

        self.alarm_box_gantry_x.addWidget(self.gantry_x_label)
        self.alarm_box_gantry_x.setAlignment(self.gantry_x_label, Qt.AlignCenter)
        self.alarm_box_gantry_x.addWidget(self.alarm_gantry_x)

        self.alarm_box_gantry_y.addWidget(self.gantry_y_label)
        self.alarm_box_gantry_y.setAlignment(self.gantry_y_label, Qt.AlignCenter)
        self.alarm_box_gantry_y.addWidget(self.alarm_gantry_y)


        self.stop_button = PyDMPushButton(label="Stop")
        self.stop_button.setMaximumHeight(120)
        self.stop_button.setMaximumWidth(120)
        self.stop_button.clicked.connect(self.stop_motors)
        self.stop_button.setStyleSheet("background: rgb(255,0,0)")

        self.advanced_button = TyphosRelatedSuiteButton()
        
        self.advanced_button.happi_names = [macros['MIRROR'].lower() + "_homs"]
        self.advanced_button.setText("Advanced")

        
        self.ui.horizontalLayout_8.addLayout(self.alarm_box_x)
        self.ui.horizontalLayout_8.addLayout(self.alarm_box_y)
        self.ui.horizontalLayout_8.addLayout(self.alarm_box_p)
        self.ui.horizontalLayout_8.addLayout(self.alarm_box_gantry_x)
        self.ui.horizontalLayout_8.addLayout(self.alarm_box_gantry_y)
        #self.ui.horizontalLayout_8.addWidget(self.alarm_x)
        #self.ui.horizontalLayout_8.addWidget(self.alarm_y)
        #self.ui.horizontalLayout_8.addWidget(self.alarm_p)
        #self.ui.horizontalLayout_8.addWidget(self.stop_button)

        #self.ui.verticalLayout_6.setAlignment(Qt.AlignCenter)
        self.ui.horizontalLayout_14.addWidget(self.advanced_button)
        self.ui.horizontalLayout_14.addSpacing(50)
        self.ui.horizontalLayout_14.addWidget(self.stop_button)
        self.ui.horizontalLayout_14.addSpacing(160)

        self.ui.setGeometry(QtCore.QRect(0,0, 360, 385))

    def stop_motors(self):
        self.x_stop.put(1)
        self.y_stop.put(1)
        self.p_stop.put(1)

    def ui_filename(self):
        # Point to our UI file
        return 'mirrorScreen.ui'

    def ui_filepath(self):
        # Return the full path to the UI file
        return path.join(path.dirname(path.realpath(__file__)), self.ui_filename())
示例#17
0
def peakup_dcm(correct_roll=True, plot=False, shutter=True, use_calib=False):
    """

    Scan the HDCM fine pitch and, optionally, roll against the ion chamber in the D Hutch

    correct_roll    <Bool>      If True, align the beam in the vertical (roll)
    plot            <Bool>      If True, plot the intensity as a function of pitch/roll
    shutter         <Bool>      If True, the shutter will be automatically opened/closed
    use_calib       <Bool>      If True, use a previous calibration as an initial guess

    """

    e_value = energy.energy.get()[1]
    pitch_old = dcm.c2_pitch.position
    roll_old = dcm.c1_roll.position

    det = [sclr1]

    ps = PeakStats(dcm.c2_pitch.name, im.name)
    ps1 = PeakStats(dcm.c1_roll.name, im.name)

    if (shutter == True):
        RE(mv(shut_b, 'Open'))
    c2pitch_kill = EpicsSignal("XF:05IDA-OP:1{Mono:HDCM-Ax:P2}Cmd:Kill-Cmd")

    # Turn off the ePID loop for the pitch motor
    # 'XF:05IDD-CT{FbPid:02}PID:on'
    c2_pid = EpicsSignal("XF:05IDD-CT{FbPid:02}PID:on")
    c2_pid.put(0)  # Turn off the ePID loop
    c2_V = EpicsSignal("XF:05ID-BI{EM:BPM1}DAC1")
    c2_V.put(3.0)  # Reset the piezo voltage to 3 V

    # pitch_lim = (-19.320, -19.370)
    # pitch_num = 51
    # roll_lim = (-4.9, -5.14)
    # roll_num = 45

    # pitch_lim = (-19.375, -19.425)
    # pitch_num = 51
    # roll_lim = (-4.9, -5.6)
    # roll_num = 51

    pitch_lim = (pitch_old - 0.025, pitch_old + 0.025)
    roll_lim = (roll_old - 0.2, roll_old + 0.2)

    pitch_num = 51
    roll_num = 51

    if (use_calib):
        # Factor to convert eV to keV
        K = 1
        if (e_value > 1000):
            K = 1 / 1000
        # Pitch calibration
        pitch_guess = -0.00055357 * K * e_value - 19.39382381
        dcm.c2_pitch.move(pitch_guess, wait=True)
        # Roll calibration
        roll_guess = -0.01124286 * K * e_value - 4.93568571
        dcm.c1_roll.move(roll_guess, wait=True)
        # Output guess
        print('\nMoving to guess:')
        print('\tC2 Pitch: %f' % (pitch_guess))
        print('\tC1 Roll:  %f\n' % (roll_guess))

    #if e_value < 10.:
    #    sclr1.preset_time.put(0.1)
    #    RE(scan([sclr1], dcm.c2_pitch, -19.335, -19.305, 31), [ps])
    #else:
    #    sclr1.preset_time.put(1.)
    #    RE(scan([sclr1], dcm.c2_pitch, -19.355, -19.310, 46), [ps])
    if e_value < 14.:
        # sclr1.preset_time.put(0.1)
        sclr1.preset_time.put(1.0)
    else:
        sclr1.preset_time.put(1.0)

    if (plot == True):
        sclr1.preset_time.put(
            1.0)  # Let's collect a longer scan since we're plotting it
        RE(scan(det, dcm.c2_pitch, pitch_lim[0], pitch_lim[1], pitch_num),
           [ps])
        print('Pitch: Centroid at %f\n\n' % (ps.cen))
        plt.figure()
        plt.plot(ps.x_data, ps.y_data, label='Data')
        plt.plot((ps.cen, ps.cen), (np.amin(ps.y_data), np.amax(ps.y_data)),
                 '--k',
                 label='Centroid')
        plt.xlabel('HDCM C2 PITCH')
        plt.ylabel('Counts')
        plt.legend()
    else:
        RE(scan(det, dcm.c2_pitch, pitch_lim[0], pitch_lim[1], pitch_num),
           [ps])

    time.sleep(0.5)
    dcm.c2_pitch.move(ps.cen, wait=True)
    time.sleep(0.5)
    if (np.abs(dcm.c2_pitch.position - ps.cen) > 0.001):
        print('The pitch motor did not move on the first try. Trying again...',
              end='')
        dcm.c2_pitch.move(ps.cen, wait=True)
        if (np.abs(dcm.c2_pitch.position - ps.cen) > 0.001):
            print('FAIL! Check motor location.\n')
        else:
            print('OK\n')
    logscan('peakup_pitch')
    c2pitch_kill.put(1)

    if correct_roll == True:
        if (plot == True):
            sclr1.preset_time.put(
                1.0)  # If we are debugging, let's collect a longer scan
            RE(scan(det, dcm.c1_roll, roll_lim[0], roll_lim[1], roll_num),
               [ps1])
            # print('Roll: Maximum flux at %f' % (ps1.max[0]))
            print('Roll: Centroid at %f\n\n' % (ps1.cen))
            plt.figure()
            plt.plot(ps1.x_data, ps1.y_data, label='Data')
            plt.plot((ps1.cen, ps1.cen),
                     (np.amin(ps1.y_data), np.amax(ps1.y_data)),
                     '--k',
                     label='Centroid')
            plt.xlabel('HDCM ROLL')
            plt.ylabel('Counts')
            plt.legend()
        else:
            RE(scan(det, dcm.c1_roll, roll_lim[0], roll_lim[1], roll_num),
               [ps1])

        time.sleep(0.5)
        dcm.c1_roll.move(ps1.cen, wait=True)
        time.sleep(0.5)
        if (np.abs(dcm.c1_roll.position - ps1.cen) > 0.001):
            print(
                'The roll motor did not move on the first try. Trying again...',
                end='')
            dcm.c1_roll.move(ps1.cen, wait=True)
            if (np.abs(dcm.c1_roll.position - ps1.cen) > 0.001):
                print('FAIL! Check motor location.\n')
            else:
                print('OK\n')
        logscan('peakup_roll')

    # Output old/new values
    print('Old pitch value:\t%f' % pitch_old)
    print('New pitch value:\t%f' % ps.cen)
    print('Current pitch value:\t%f' % dcm.c2_pitch.position)
    print('Old roll value: \t%f' % roll_old)
    print('New roll value: \t%f' % ps1.cen)
    print('Current roll value: \t%f\n' % dcm.c1_roll.position)

    if (shutter == True):
        RE(mv(shut_b, 'Close'))

    #for some reason we now need to kill the pitch motion to keep it from overheating.  6/8/17
    #this need has disappeared mysteriously after the shutdown - gjw 2018/01/19
    # This has now reappeared - amk 2018/06/06
    time.sleep(1)
    c2pitch_kill.put(1)
    c2_pid.put(1)  # Turn on the ePID loop
class RamperIOC(PVGroup):
    """
    An IOC for executing ramps of pv_setpoints

    """
    pv_sp = pvproperty(
        value=25.0,
        doc='pv setpoint',
    )

    pv_sp_rate = pvproperty(
        value=0.0,
        doc='pv setpoint change rate',
    )

    go = pvproperty(
        value=0,
        doc='flag indicating whether ramping is actually taking place')

    time_elapsed = pvproperty(
        value=0.0,
        doc=
        'timer for measuring the elapsed time since the beginning of the program'
    )

    pause = pvproperty(value=0,
                       doc='flag indicating whether ramping is paused')

    time_paused = pvproperty(value=0.0,
                             doc='timer for measuring the paused time')

    tprog = pvproperty(value=[0.0, 60.0],
                       doc='time array of the ramp program',
                       max_length=25)

    pvprog = pvproperty(value=[25.0, 25.0],
                        doc='pv setpoint array of the ramp program',
                        max_length=25)

    dwell = pvproperty(value=0.05, doc="dwell time for the pv setpoint update")

    safety_timer = pvproperty(
        value=0.0,
        doc='timer for stopping the program in case if ipython session dies')

    safety_thresh = pvproperty(value=10.0,
                               doc='time threshold for turning the heater off')

    pid_enable_name = pvproperty(value='',
                                 dtype=ChannelType.STRING,
                                 doc='pv name for pid enable')

    pid_output_name = pvproperty(
        value='',
        dtype=ChannelType.STRING,
        # dtype=_LongStringChannelType.LONG_STRING,
        doc='pv name for pid output')

    # pid_output_name_ext = pvproperty(
    #     value='',
    #     dtype=ChannelType.STRING,
    #     # dtype=_LongStringChannelType.LONG_STRING,
    #     doc='pv name for pid output (extension)'
    # )

    pv_test = pvproperty(value=0, doc='pv for output testing')
    _previous_elapsed_time = None
    time_start = None
    pid_enable = None
    pid_output = None

    @pv_sp.startup
    async def pv_sp(self, instance, async_lib):
        """This is a startup hook which periodically updates the value."""
        rate = 0
        while True:
            await self.pv_sp_rate.write(rate)
            await async_lib.sleep(self.dwell.value)
            if self.go.value == 1:
                if self.time_start is None:
                    self.time_start = ttime.time()

                if self.pause.value == 0:

                    await self.time_elapsed.write(ttime.time() -
                                                  self.time_start)
                    dt = self.time_elapsed.value - self.time_paused.value

                    if dt < np.max(self.tprog.value):
                        pv_sp = np.interp(dt, self.tprog.value,
                                          self.pvprog.value)
                    else:
                        pv_sp = self.pvprog.value[-1]

                    if self._previous_elapsed_time is not None:
                        rate = (pv_sp - self.pv_sp.value) / (
                            dt - self._previous_elapsed_time) * 60

                    self._previous_elapsed_time = dt
                    await instance.write(value=pv_sp)
                else:
                    rate = 0

            else:
                self.time_start = None
                await self.time_elapsed.write(0.0)
                await self.time_paused.write(0.0)
                rate = 0

    @pid_enable_name.startup
    async def pid_enable_name(self, instance, async_lib):
        while self.pid_enable_name.value == '':
            await async_lib.sleep(1)

        self.pid_enable = EpicsSignal(self.pid_enable_name.value,
                                      name='pid_enable')

        def subscription(value, **kwargs):
            if value == 0:
                if self.pid_output is not None:
                    if self.pid_output.get() != 0:
                        self.pid_output.put(0)

        self.pid_enable.subscribe(subscription)

    @pid_output_name.startup
    async def pid_output_name(self, instance, async_lib):
        while (self.pid_output_name.value == ''):
            await async_lib.sleep(1)

        self.pid_output = EpicsSignal(self.pid_output_name.value,
                                      name='pid_output')

        def subscription(value, **kwargs):
            if value != 0:
                if self.pid_enable is not None:
                    if self.pid_enable.get() == 0:
                        self.pid_output.put(0)

        self.pid_output.subscribe(subscription)

    @safety_timer.startup
    async def safety_timer(self, instance, async_lib):
        while self.safety_timer.value < self.safety_thresh.value:
            safety_timer = self.safety_timer.value + 1
            await instance.write(value=safety_timer)
            await async_lib.sleep(1)
        if self.pid_enable is not None:
            self.pid_enable.put(0)

    @time_paused.startup
    async def time_paused(self, instance, async_lib):
        while True:
            cur_time = ttime.time()
            if self.pause.value == 1:
                while True:
                    if self.pause.value == 0:
                        break
                    await async_lib.sleep(self.dwell.value / 2)
                time_paused = self.time_paused.value + (ttime.time() -
                                                        cur_time)
                await instance.write(value=time_paused)

            await async_lib.sleep(self.dwell.value)
示例#19
0
class ICAmplifier(Device):
    """Keithely 428 Current Amplifier Ophyd device"""
    def __init__(self, *args, gain_set_pv, gain_get_pv, autoFilter_set_pv,
                 autoFilter_get_pv, riseTime_set_pv, riseTime_get_pv,
                 zcheck_set_pv, zcheck_get_pv, filter_set_pv, filter_get_pv,
                 x10_set_pv, x10_get_pv, autoSupEnable_set_pv,
                 autoSupEnable_get_pv, suppression_enable_set_pv,
                 suppression_enable_get_pv, suppressionValue_set_pv,
                 suppressionExponent_set_pv, suppression_set_pv,
                 suppression_get_pv, overload_get_pv, **kwargs):

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

        self.gain = EpicsSignal(write_pv=self.prefix + gain_set_pv,
                                read_pv=self.prefix + gain_get_pv,
                                auto_monitor=True,
                                name=self.name)

        self.autoFilter = EpicsSignal(write_pv=self.prefix + autoFilter_set_pv,
                                      read_pv=self.prefix + autoFilter_get_pv,
                                      auto_monitor=True,
                                      name=self.name)

        self.filter = EpicsSignal(write_pv=self.prefix + filter_set_pv,
                                  read_pv=self.prefix + filter_get_pv,
                                  auto_monitor=True,
                                  name=self.name)

        self.riseTime = EpicsSignal(write_pv=self.prefix + riseTime_set_pv,
                                    read_pv=self.prefix + riseTime_get_pv,
                                    auto_monitor=True,
                                    name=self.name)

        self.zeroCheck = EpicsSignal(write_pv=self.prefix + zcheck_set_pv,
                                     read_pv=self.prefix + zcheck_get_pv,
                                     auto_monitor=True,
                                     name=self.name)

        self.x10 = EpicsSignal(write_pv=self.prefix + x10_set_pv,
                               read_pv=self.prefix + x10_get_pv,
                               auto_monitor=True,
                               name=self.name)

        self.suppressionValue = EpicsSignal(
            write_pv=self.prefix + suppressionValue_set_pv,
            read_pv=self.prefix + suppressionValue_set_pv,
            auto_monitor=True,
            name=self.name)

        self.suppressionExponent = EpicsSignal(
            write_pv=self.prefix + suppressionExponent_set_pv,
            read_pv=self.prefix + suppressionExponent_set_pv,
            auto_monitor=True,
            name=self.name)

        self.autoSupEnable = EpicsSignal(
            write_pv=self.prefix + autoSupEnable_set_pv,
            read_pv=self.prefix + autoSupEnable_get_pv,
            auto_monitor=True,
            name=self.name)

        self.suppression = EpicsSignal(
            write_pv=self.prefix + suppression_set_pv,
            read_pv=self.prefix + suppression_get_pv,
            auto_monitor=True,
            name=self.name)

        self.overload = EpicsSignalRO(read_pv=self.prefix + overload_get_pv,
                                      auto_monitor=True,
                                      name=self.name)

    def set_gain(self, value: int):
        val = int(value)
        self.gain.put(val)

    def get_gain(self):
        return self.gain.get()

    def set_suppress(self, value: int):
        """Set current suppression depending on gain"""
        if value == 0:
            # Suppression : -0.05E-3
            self.suppressionValue.set(-0.05)
            self.suppressionExponent.set(6)
        elif value == 1:
            # Suppression : -5E-6
            self.suppressionValue.set(-5)
            self.suppressionExponent.set(3)
        elif value == 2:
            # Suppression : -0.5E-6
            self.suppressionValue.set(-0.5)
            self.suppressionExponent.set(3)
        elif value == 3:
            # Suppression : -0.05E-6
            self.suppressionValue.set(-0.05)
            self.suppressionExponent.set(3)
        elif value == 4:
            # Suppression : -5E-9
            self.suppressionValue.set(-5)
            self.suppressionExponent.set(0)
        elif value == 5:
            # Suppression : -0.5E-9
            self.suppressionValue.set(-0.5)
            self.suppressionExponent.set(0)
        elif value == 6:
            # Suppression : -0.05E-9
            self.suppressionValue.set(-0.05)
            self.suppressionExponent.set(0)
        elif value == 7:
            # Suppression : -0.005E-9
            self.suppressionValue.set(-0.005)
            self.suppressionExponent.set(0)
        else:
            pass

    def do_correct(self):
        self.zeroCheck.put(2)

    def set_zcheck(self, value: int):
        val = int(value)
        self.zeroCheck.put(val)

    def get_zcheck(self):
        return self.zeroCheck.enum_strs[self.zeroCheck.get()]
示例#20
0
class ICAmplifier(Device):
    #low_noise_gain = Cpt(EpicsSignal, 'LN}I0')

    def __init__(self, *args, gain_0, gain_1, gain_2, hspeed_bit, bw_10mhz_bit, bw_1mhz_bit, lnoise, hspeed, bwidth, par = None, **kwargs):
        super().__init__(*args, **kwargs)
        self.gain_0 = EpicsSignal(self.prefix + gain_0, name=self.name + '_gain_0')
        self.gain_1 = EpicsSignal(self.prefix + gain_1, name=self.name + '_gain_1')
        self.gain_2 = EpicsSignal(self.prefix + gain_2, name=self.name + '_gain_2')
        self.hspeed_bit = EpicsSignal(self.prefix + hspeed_bit, name=self.name + '_hspeed_bit')
        self.bw_10mhz_bit = EpicsSignal(self.prefix + bw_10mhz_bit, name=self.name + '_bw_10mhz_bit')
        self.bw_1mhz_bit = EpicsSignal(self.prefix + bw_1mhz_bit, name=self.name + '_bw_1mhz_bit')
        self.low_noise_gain = EpicsSignal(self.prefix + lnoise, name=self.name + '_lnoise')
        self.high_speed_gain = EpicsSignal(self.prefix + hspeed, name=self.name + '_hspeed')
        self.band_width = EpicsSignal(self.prefix + bwidth, name=self.name + '_bwidth')
        self.par = par

    def set_gain(self, value: int, high_speed: bool):

        val = int(value) - 2
        if not ((high_speed and (1 <= val < 7)) or (not high_speed and (0 <= val < 6))):
            print('{} invalid value. Ignored...'.format(self.name))
            return 'Aborted'

        if high_speed:
            val -= 1
            self.low_noise_gain.put(0)
            self.high_speed_gain.put(val + 1)
            self.hspeed_bit.put(1)
        else:
            self.low_noise_gain.put(val + 1)
            self.high_speed_gain.put(0)
            self.hspeed_bit.put(0)

        self.gain_0.put((val >> 0) & 1)
        self.gain_1.put((val >> 1) & 1)
        self.gain_2.put((val >> 2) & 1)

    def set_gain_plan(self, value: int, high_speed: bool):

        val = int(value) - 2
        if not ((high_speed and (1 <= val < 7)) or (not high_speed and (0 <= val < 6))):
            print('{} invalid value. Ignored...'.format(self.name))
            return 'Aborted'

        if high_speed:
            val -= 1
            yield from bps.abs_set(self.low_noise_gain, 0)
            yield from bps.abs_set(self.high_speed_gain, val + 1)
            yield from bps.abs_set(self.hspeed_bit, 1)
        else:
            yield from bps.abs_set(self.low_noise_gain, val + 1)
            yield from bps.abs_set(self.high_speed_gain, 0)
            yield from bps.abs_set(self.hspeed_bit, 0)

        yield from bps.abs_set(self.gain_0, (val >> 0) & 1)
        yield from bps.abs_set(self.gain_1, (val >> 1) & 1)
        yield from bps.abs_set(self.gain_2, (val >> 2) & 1)

    def get_gain(self):
        if self.low_noise_gain.value == 0:
            return [self.high_speed_gain.enum_strs[self.high_speed_gain.value], 1]
        elif self.high_speed_gain.value == 0:
            return [self.low_noise_gain.enum_strs[self.low_noise_gain.value], 0]
        else:
            return ['0', 0]
示例#21
0
文件: lu4417.py 项目: pcdshub/mfx
class User:
    SCALE = 1000.0
    """Generic User Object"""
    sequencer = sequencer

    def __init__(self):
        self.mesh_raw = EpicsSignal(name='mesh_raw',
                                    read_pv='MFX:USR:ai1:0',
                                    write_pv='MFX:USR:ao1:0')

    @property
    def current_rate(self):
        """Current configured EventSequencer rate"""
        return sequencer.sync_marker.get(as_string=True)

    def configure_sequencer(self, rate='30Hz', readout_all=False):
        """
        Setup EventSequencer (30Hz default) with event codes to tag
        the Rayonix readout and each of the preceeding 3 shots.

        Parameters
        ----------
        rate : str, optional
            default is "30Hz", or optionally "10Hz"

        readout_all : bool, optional
            Readout all events with ec198 if True
            otherwise readout only ec198 at same time as Rayonix ec210

        """
        logger.info("Configure EventSequencer ...")
        # Setup sequencer
        sequencer.sync_marker.put(rate)
        if rate == '10Hz':
            delta0 = 120 / 10
        elif rate == '30Hz':
            delta0 = 120 / 30
        else:
            delta0 = 120 / 30

        # Set sequence
        #   delta0 is the number of shots until the next event code
        #   for the desired rate, e.g., ec42 for 30 Hz and ec43 for 10 Hz
        #
        seqstep = 0
        seq_steps[seqstep].configure({
            'eventcode': 213,
            'delta_beam': delta0 - 3,
            'fiducial': 0,
            'comment': 'Rayonix-3'
        })
        seqstep += 1
        seq_steps[seqstep].configure({
            'eventcode': 197,
            'delta_beam': 1,
            'fiducial': 0,
            'comment': 'PulsePicker'
        })
        if readout_all:
            seqstep += 1
            seq_steps[seqstep].configure({
                'eventcode': 198,
                'delta_beam': 0,
                'fiducial': 0,
                'comment': 'DAQ Readout'
            })

        seqstep += 1
        seq_steps[seqstep].configure({
            'eventcode': 212,
            'delta_beam': 0,
            'fiducial': 0,
            'comment': 'Rayonix-2'
        })
        if readout_all:
            seqstep += 1
            seq_steps[seqstep].configure({
                'eventcode': 198,
                'delta_beam': 0,
                'fiducial': 0,
                'comment': 'DAQ Readout'
            })

        seqstep += 1
        seq_steps[seqstep].configure({
            'eventcode': 211,
            'delta_beam': 1,
            'fiducial': 0,
            'comment': 'Rayonix-1'
        })
        if readout_all:
            seqstep += 1
            seq_steps[seqstep].configure({
                'eventcode': 198,
                'delta_beam': 0,
                'fiducial': 0,
                'comment': 'DAQ Readout'
            })

        seqstep += 1
        seq_steps[seqstep].configure({
            'eventcode': 210,
            'delta_beam': 1,
            'fiducial': 0,
            'comment': 'Rayonix'
        })
        if readout_all:
            seqstep += 1
            seq_steps[seqstep].configure({
                'eventcode': 198,
                'delta_beam': 0,
                'fiducial': 0,
                'comment': 'DAQ Readout'
            })

        seqstep += 1
        seq_steps[seqstep].configure({
            'eventcode': 198,
            'delta_beam': 0,
            'fiducial': 0,
            'comment': 'DAQ Readout'
        })
        # Clear other sequence steps
        seqstep += 1
        sequencer.sequence_length.put(seqstep)
        for i in range(seqstep, 20):
            seq_steps[i].clear()

    ######################
    # Scanning Functions #
    ######################
    def perform_run(self,
                    events,
                    record=True,
                    comment='',
                    post=True,
                    **kwargs):
        """
        Perform a single run of the experiment

        Parameters
        ----------
        events: int
            Number of events to include in run

        record: bool, optional
            Whether to record the run

        comment : str, optional
            Comment for ELog

        post: bool, optional
            Whether to post to the experimental ELog or not. Will not post if
            not recording

        """
        # time.sleep(3) / a leftover from original script
        # Create descriptive message
        comment = comment or ''
        # Start recording
        try:
            logger.info("Starting DAQ run, -> record=%s", record)
            daq.begin(events=events, record=record)
            time.sleep(
                2)  # Wait for the DAQ to get spinnign before sending events
            logger.debug("Starting EventSequencer ...")
            sequencer.start()
            time.sleep(1)
            # Post to ELog if desired
            runnum = daq._control.runnumber()
            info = [runnum, comment, events, self.current_rate]
            post_msg = post_template.format(*info)
            print(post_msg)
            if post and record:
                elog.post(post_msg, run=runnum)
            # Wait for the DAQ to finish
            logger.info("Waiting or DAQ to complete %s events ...", events)
            daq.wait()
            logger.info("Run complete!")
            daq.end_run()
            logger.debug("Stopping Sequencer ...")
            sequencer.stop()
            # allow short time after sequencer stops
            time.sleep(0.5)
        except KeyboardInterrupt:
            logger.warning("Scan interrupted by user request!")
            logger.info("Stopping DAQ ...")
            daq.stop()
        # Return the DAQ to the original state
        finally:
            logger.info("Disconnecting from DAQ ...")
            daq.disconnect()
            logger.info("Closing all laser shutters ...")
            self.configure_shutters(pulse1=False,
                                    pulse2=False,
                                    pulse3=False,
                                    opo=False)
            logger.info("Restarting the EventSequencer ...")
            sequencer.start()

    def get_mesh_voltage(self):
        """
        Get the current power supply voltage
        """
        return self.mesh_raw.get()

    def set_mesh_voltage(self, sigIn, wait=True, do_print=True):
        """
        Set voltage on power supply to an absolute value

        Parameters
        ----------
        sigIn: int or float
            Power supply voltage in Volts
        """
        if do_print:
            print('Setting voltage...')
        sigInScaled = sigIn / self.SCALE  # in V
        self.mesh_raw.put(sigInScaled)
        if wait:
            time.sleep(2.5)
        finalVolt = self.mesh_raw.get()
        finalVoltSupply = finalVolt * self.SCALE
        if do_print:
            print('Power Supply Setpoint: %s V' % sigIn)
            print('Power Supply Voltage: %s V' % finalVoltSupply)

    def set_rel_mesh_voltage(self, deltaVolt, wait=True, do_print=True):
        """
        Increase/decrease power supply voltage by a specified amount

        Parameters
        ----------
        deltaVolt: int or float
            Amount to increase/decrease voltage (in Volts) from
            its current value. Use positive value to increase
            and negative value to decrease
        """
        if do_print:
            print('Setting voltage...')
        curr_set = self.mesh_raw.get_setpoint()
        curr_set_supply = curr_set * self.SCALE
        if do_print:
            print('Previous Power Supply Setpoint: %s V' % curr_set_supply)
        new_voltage = round(curr_set_supply + deltaVolt)
        self.set_mesh_voltage(new_voltage, wait=wait, do_print=do_print)

    def tweak_mesh_voltage(self, deltaVolt):
        """
        Continuously Increase/decrease power supply voltage by
        specifed amount using arrow keys

        Parameters
        ----------
        deltaVolt: int or float
            Amount to change voltage (in Volts) from its current value at
            each step. After calling with specified step size, use arrow keys
            to keep changing
        ^C:
            exits tweak mode
        """
        print('Use arrow keys (left, right) to step voltage (-, +)')
        while True:
            key = key_press.get_input()
            if key in ('q', None):
                return
            elif key == key_press.arrow_right:
                self.set_rel_mesh_voltage(deltaVolt,
                                          wait=False,
                                          do_print=False)
            elif key == key_press.arrow_left:
                self.set_rel_mesh_voltage(-deltaVolt,
                                          wait=False,
                                          do_print=False)
class PilatusExtTrigger(PilatusDetector):
    armed = Cpt(EpicsSignal, "cam1:Armed")

    # Use self._image_name as in SingleTrigger?
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._num_images = 1
        self._acquisition_signal = self.cam.acquire
        self._counter_signal = self.cam.array_counter
        #self._trigger_signal = EpicsSignal('XF:16ID-TS{EVR:C1-Out:FP3}Src:Scale-SP')
        self._trigger_signal = EpicsSignal('XF:16IDC-ES{Zeb:1}:SOFT_IN:B0')

        self._status = None
        self.first = True
        self.acq_t = 0

    def set_num_images(self, num_images):
        self._num_images = num_images

    def stage(self, multitrigger=True):
        if self._staged == Staged.yes:
            return

        print(self.name, "staging")
        #self.stage_sigs.update([
        #    ('cam.trigger_mode', 3), # 2 DOESN'T WORK!
        #    ('cam.num_images', self._num_images)
        #])

        acq_t = self.cam.acquire_period.get()

        if multitrigger:
            self.cam.trigger_mode.put(3, wait=True)  # ExtMTrigger in camserver
            self.trig_wait = acq_t + 0.02
        else:
            self.cam.trigger_mode.put(2, wait=True)  # ExtTrigger in camserver
            self.trig_wait = acq_t * self._num_images + 0.02

        time.sleep(.1)

        self.cam.num_images.put(self._num_images, wait=True)
        print(self.name, "stage sigs updated")
        super().stage()
        print(self.name, "super staged")
        self._counter_signal.put(0)

        print(self.name, "checking armed status")
        self._acquisition_signal.put(1)  #, wait=True)
        while self.armed.get() != 1:
            time.sleep(0.1)

        print(self.name, "staged")

    def unstage(self):
        if self._staged == Staged.no:
            return

        self._status = None
        self._acquisition_signal.put(0)

        time.sleep(.1)
        self.cam.trigger_mode.put(0, wait=True)
        time.sleep(.1)
        self.cam.num_images.put(1, wait=True)
        super().unstage()

    def trigger(self):
        print(self.name + " trigger")
        if self._staged != Staged.yes:
            raise RuntimeError("This detector is not ready to trigger."
                               "Call the stage() method before triggering.")

        status = DeviceStatus(self)
        # Only one Pilatus has to send the trigger
        if self.name == first_PilatusExt():
            while pilatus_trigger_lock.locked():
                time.sleep(0.005)
            print("triggering")
            self._trigger_signal.put(1, wait=True)
            self._trigger_signal.put(0, wait=True)
            ##set up callback to clear status after the end-of-exposure
            threading.Timer(self.trig_wait, status._finished, ()).start()
        else:
            status._finished()

        self.dispatch(f'{self.name}_image', ttime.time())
        return status