Esempio n. 1
0
 def __init__(self, pvName, mnemonic):
     StandardDevice.__init__(self, mnemonic)
     self.pvName = pvName
     self.modem = Device(pvName + ':FONE:',
                         ('discar.PROC', 'audio', 'numero', 'discar.VALA'))
     self._status = self.getStatus()
     self.modem.add_callback('discar.VALA', self.onStatusChange)
Esempio n. 2
0
    def __init__(self, pvName, mnemonic, timeBased=False):
        """
        **Constructor**
        To use this Keithley Class you must pass the PV (Process Variable) prefix.

            .. Note::
                e.g.: SXS:K6514

        Examples
        --------

        >>> from KeithleyClass import *
        >>> name = Keithley('SOL:K6514', 'k1')

        """
        Keithley6514.__init__(self, pvName, mnemonic)
        self.pvName = pvName
        self.timeBased = timeBased
        self.keithley = Device(
            pvName + ':',
            ('GetMed', 'SetMed', 'GetMedRank', 'SetMedRank', 'GetAver',
             'SetAver', 'GetAverCoun', 'SetAverCoun', 'GetNPLC', 'SetNPLC',
             'GetAutoZero', 'SetAutoZero', 'GetZeroCheck', 'SetZeroCheck',
             'GetAverTCon', 'SetAverTCon', 'GetRange', 'SetRange',
             'GetZeroCor', 'SetZeroCor', 'GetAutoCurrRange', 'SetAutoCurrRange'
             'Count', 'ContinuesMode', 'CNT', 'OneMeasure'))

        self.pvMeasure = PV(pvName + ':' + 'Measure', auto_monitor=False)
        self._counting = self.isCountingPV()
        self.keithley.add_callback('CNT', self.onStatusChange)
Esempio n. 3
0
    def __init__(self,
                 prefix,
                 nmcas=4,
                 filesaver='HDF1:',
                 fileroot='/home/xspress3'):
        dt = debugtime()
        self.nmcas = nmcas
        attrs = []
        attrs.extend(['%s%s' % (filesaver, p) for p in self.pathattrs])

        self.filesaver = filesaver
        self.fileroot = fileroot
        self._prefix = prefix
        self.mcas = []
        for i in range(nmcas):
            imca = i + 1
            dprefix = "%sdet1:" % prefix
            rprefix = "%sMCA%iROI" % (prefix, imca)
            data_pv = "%sMCA%i:ArrayData" % (prefix, imca)
            mca = ADMCA(dprefix, data_pv=data_pv, roi_prefix=rprefix)
            self.mcas.append(mca)
            attrs.append("MCA%iROI:TSControl" % (imca))
            attrs.append("MCA%iROI:TSNumPoints" % (imca))
            attrs.append("C%iSCA:TSControl" % (imca))
            attrs.append("C%iSCA:TSNumPoints" % (imca))
        Device.__init__(self, prefix, attrs=attrs, delim='')
        for attr in self.det_attrs:
            self.add_pv("%sdet1:%s" % (prefix, attr), attr)
        for i in range(nmcas):
            imca = i + 1
            for j in range(8):
                isca = j + 1
                attr = "C%iSCA%i" % (imca, isca)
                self.add_pv("%s%s:Value_RBV" % (prefix, attr), attr)
        poll(0.003, 0.25)
 def set_prefix(self, prefix):
     self.prefix = prefix
     self.ad_img = Device(prefix + ':image1:',
                          delim='',
                          attrs=self.img_attrs)
     self.title.SetLabel("Fly2AD: %s" % prefix)
     self.connect_pvs()
Esempio n. 5
0
    def __init__(self, pvPrefix="", mnemonic="", channel=0):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        pvPrefix : `string`
            LakeShore331's device base naming of the PV (Process Variable); Like DXAS:LS331;
        mnemonic : `string`
            LakeShore331's mnemonic
        """
        StandardDevice.__init__(self, mnemonic)
        self.lakeshore331 = Device(
            pvPrefix + ':',
            ('GetHEAT', 'GetHeaterRange', 'GetAPIDD', 'GetAPIDI', 'GetAPIDP',
             'GetASetPoint', 'GetBPIDD', 'GetBPIDI', 'GetBPIDP',
             'GetBSetPoint', 'GetCTempA', 'GetCTempB', 'GetKTempA',
             'GetKTempB', 'SetHeaterRange', 'SetAPIDD', 'SetAPIDI', 'SetAPIDP',
             'SetASetPoint', 'SetBPIDD', 'SetBPIDI', 'SetBPIDP',
             'SetBSetPoint', 'GetCmode', 'SetCmode'))
        self.ls331_control = Device(pvPrefix + ':CONTROL:',
                                    ['SetAPID', 'SetBPID', 'Trigger'])

        if (channel == 1):
            self.ls331_channel = LakeShore_t.Channel_B
        else:
            # Default
            self.ls331_channel = LakeShore_t.Channel_A
Esempio n. 6
0
    def __init__(self, prefix, nmca=4, filesaver='HDF1:',
                 fileroot='/home/xspress3/cars5/Data'):
        if not prefix.endswith(':'):
            prefix = "%s:" % prefix
        self.nmca = nmca
        attrs = []
        attrs.extend(['%s%s' % (filesaver,p) for p in self.pathattrs])

        self.filesaver = filesaver
        self.fileroot = fileroot
        self._prefix = prefix
        self.mcas = []
        for i in range(nmca):
            imca = i+1
            dprefix = "%sdet1:" % prefix
            rprefix = "%sMCA%iROI" % (prefix, imca)
            data_pv = "%sMCA%i:ArrayData" % (prefix, imca)
            mca = ADMCA(dprefix, data_pv=data_pv, roi_prefix=rprefix)
            self.mcas.append(mca)

        Device.__init__(self, prefix, attrs=attrs, delim='')
        for attr in self.det_attrs:
            self.add_pv("%sdet1:%s" % (prefix, attr), attr)
        for i in range(nmca):
            imca = i+1
            for j in range(8):
                isca = j+1
                attr="C%iSCA%i"% (imca, isca)
                self.add_pv("%s%s:Value_RBV" % (prefix, attr), attr)
            for attr in ('TSNumPoints', 'TSControl'):
                self.add_pv("%sMCA%iROI:%s" % (prefix, imca, attr),
                            "MCA%i%s" % (imca, attr))
                self.add_pv("%sC%iSCA:%s" % (prefix, imca, attr),
                            "SCA%i%s" % (imca, attr))
        time.sleep(0.05)
Esempio n. 7
0
    def __init__(self, prefix, nchan=4, sis_prefix=None):

        self._mode = SCALER_MODE
        self.ROIMode = self.NDArrayMode
        self._chans = range(1, nchan + 1)

        attrs = list(self.attrs)
        for i in self._chans:
            for a in self.curr_attrs:
                attrs.append(("Current" + a) % i)

        Device.__init__(self, prefix, delim='', attrs=attrs, mutable=False)
        self._aliases = {}
        for i in self._chans:
            self._aliases['Current%i' % i] = 'Current%i:MeanValue_RBV' % i
            self._aliases['Sigma%i' % i] = 'Current%i:Sigma_RBV' % i
            self._aliases['Offset%i' % i] = 'CurrentOffset%i' % i
            self._aliases['Scale%i' % i] = 'CurrentScale%i' % i
            self._aliases['Name%i' % i] = 'CurrentName%i' % i
            self._aliases['TSControl%i' % i] = 'Current%i:TSControl' % i
            self._aliases['TSAcquiring%i' % i] = 'Current%i:TSAcquiring' % i
            self._aliases['TSNumPoints%i' % i] = 'Current%i:TSNumPoints' % i
            self._aliases['TSTotal%i' % i] = 'Current%i:TSTotal' % i
            self._aliases['TSSigma%i' % i] = 'Current%i:TSSigma' % i

        self._sis = None
        if sis_prefix is not None:
            self.sis_prefix = sis_prefix
            self._sis = Struck(prefix)
Esempio n. 8
0
    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`
        
        Parameters
        ----------
        pvName : `string`
            Motor's base naming of the PV (Process Variable)
        mnemonic : `string`
            Motor's mnemonic
        """
        StandardDevice.__init__(self, mnemonic)
        self.pvName = pvName

        self.pvType = PV(pvName + ".RTYP", connection_timeout=3)

        if (self.pvType.status is None):
            raise Exception("Epics PV " + pvName +
                            " seems to be offline or not reachable")

        if (self.pvType.get() != "motor"):
            raise Exception(pvName + " is not an Epics Motor")

        self.motor = Device(
            pvName + '.',
            ('RBV', 'VAL', 'DRBV', 'DVAL', 'RLV', 'RVAL', 'RRBV', 'STOP',
             'MOVN', 'LLS', 'HLS', 'SSET', 'SUSE', 'SET', 'VELO', 'EGU',
             'DMOV', 'STUP', 'DESC', 'BDST', 'HLM', 'LLM', 'DHLM', 'DLLM',
             'VOF', 'FOF', 'OFF', 'DIR', 'LVIO', 'HOMF', 'HOMR'))

        self.motor.add_callback('DMOV', self.onStatusChange)
        self._moving = self.isMovingPV()

        self.motorDesc = self.getDescription()
Esempio n. 9
0
    def __init__(self, prefix, nmca=4, filesaver='HDF1:',
                 fileroot='/home/xspress3/cars5/Data'):
        if not prefix.endswith(':'):
            prefix = "%s:" % prefix
        self.nmca = nmca
        attrs = []
        attrs.extend(['%s%s' % (filesaver,p) for p in self.pathattrs])

        self.filesaver = filesaver
        self.fileroot = fileroot
        self._prefix = prefix
        self.mcas = []
        for i in range(nmca):
            imca = i+1
            dprefix = "%sdet1:" % prefix
            rprefix = "%sMCA%iROI" % (prefix, imca)
            data_pv = "%sMCA%i:ArrayData" % (prefix, imca)
            mca = ADMCA(dprefix, data_pv=data_pv, roi_prefix=rprefix)
            self.mcas.append(mca)

        Device.__init__(self, prefix, attrs=attrs, delim='')
        for attr in self.det_attrs:
            self.add_pv("%sdet1:%s" % (prefix, attr), attr)
        for i in range(nmca):
            imca = i+1
            for j in range(8):
                isca = j+1
                attr="C%iSCA%i"% (imca, isca)
                self.add_pv("%s%s:Value_RBV" % (prefix, attr), attr)
            for attr in ('TSNumPoints', 'TSControl'):
                self.add_pv("%sMCA%iROI:%s" % (prefix, imca, attr),
                            "MCA%i%s" % (imca, attr))
                self.add_pv("%sC%iSCA:%s" % (prefix, imca, attr),
                            "SCA%i%s" % (imca, attr))
        time.sleep(0.05)
Esempio n. 10
0
    def __init__(self, prefix, nchan=4, sis_prefix=None):
        if not prefix.endswith(':'):
            prefix = "%s:" % prefix
        self._mode = SCALER_MODE
        self.ROIMode = self.NDArrayMode
        self._chans = range(1, nchan+1)

        attrs = list(self.attrs)
        for i in self._chans:
            for a in self.curr_attrs:
                attrs.append(("Current" + a) % i)

        Device.__init__(self, prefix, delim='', attrs=attrs, mutable=False)
        self._aliases = {}
        for i in self._chans:
            self._aliases['Current%i'% i] = 'Current%i:MeanValue_RBV' % i
            self._aliases['Sigma%i'% i] = 'Current%i:Sigma_RBV' % i
            self._aliases['Offset%i'% i] = 'CurrentOffset%i' % i
            self._aliases['Scale%i'% i] = 'CurrentScale%i' % i
            self._aliases['Name%i'% i] = 'CurrentName%i' % i
            self._aliases['TSControl%i'% i] = 'Current%i:TSControl' % i
            self._aliases['TSAcquiring%i'% i] = 'Current%i:TSAcquiring' % i
            self._aliases['TSNumPoints%i'% i] = 'Current%i:TSNumPoints' % i
            self._aliases['TSTotal%i'% i] = 'Current%i:TSTotal' % i
            self._aliases['TSSigma%i'% i] = 'Current%i:TSSigma' % i

        self._sis = None
        if sis_prefix is not None:
            self.sis_prefix = sis_prefix
            self._sis = Struck(prefix)
Esempio n. 11
0
    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        pvName : `string`
            Power supply base naming of the PV (Process Variable)
        mnemonic : `string`
            Temperature controller mnemonic
        """
        super().__init__(mnemonic)

        self.device = Device(pvName + ':', ['termopar', 'target', 'status', 'stepNum',
                             'programTable', 'programming', 'run', 'stop', 'advance',
                             'setPatternCount', 'timeScale', 'level1', 'reset', 'pause',
                             'sendCommand', 'pidtable', 'numPIDElements', 'paused', 'getP',
                             'getI', 'getD', 'power'])

        self.programmingDone = Event()
        self.newTemperature = Event()
        self.newStep = Event()
        self.device.add_callback('programming', self.onProgrammingChange)
        self.device.add_callback('termopar', self.onTemperatureChange)
        self.device.add_callback('stepNum', self.onStepChange)
        self.timeScaleCache = self.device.get('timeScale')

        self.pvName = pvName
        self.rate = 5
        self.presetDone = False
Esempio n. 12
0
    def __init__(self, prefix, scaler=None, nchan=8, clockrate=50.0):
        self._nchan = nchan
        self.scaler = None
        self.clockrate = clockrate  # clock rate in MHz
        self._mode = SCALER_MODE

        if scaler is not None:
            self.scaler = Scaler(scaler, nchan=nchan)

        self.mcas = []
        for i in range(nchan):
            self.mcas.append(MCA(prefix, mca=i + 1, nrois=2))

        Device.__init__(self,
                        prefix,
                        delim='',
                        attrs=self.attrs,
                        mutable=False)

        time.sleep(0.05)
        for pvname, pv in self._pvs.items():
            pv.get()

        self.ast_interp = asteval.Interpreter()
        self.read_scaler_config()
Esempio n. 13
0
    def __init__(self, prefix, nmcas=4, filesaver='HDF1:',
                 fileroot='/T/xas_user'):
        dt = debugtime()
        if not prefix.endswith(':'):
            prefix = "%s:" % prefix
        self.nmcas = nmcas
        attrs = []
        attrs.extend(['%s%s' % (filesaver, p) for p in self.pathattrs])

        self.filesaver = filesaver
        self.fileroot = fileroot
        self._prefix = prefix
        self.mcas = []
        for i in range(nmcas):
            imca = i+1
            dprefix = "%sdet1:" % prefix
            rprefix = "%sMCA%iROI" % (prefix, imca)
            data_pv = "%sMCA%i:ArrayData" % (prefix, imca)
            mca = ADMCA(dprefix, data_pv=data_pv, roi_prefix=rprefix)
            self.mcas.append(mca)
            attrs.append("%s:MCA%iROI:TSControl" % (prefix, imca))
            attrs.append("%s:MCA%iROI:TSNumPoints" % (prefix, imca))
            attrs.append("%s:C%iSCA:TSControl" % (prefix, imca))
            attrs.append("%s:C%iSCA:TSNumPoints" % (prefix, imca))
        Device.__init__(self, prefix, attrs=attrs, delim='')
        for attr in self.det_attrs:
            self.add_pv("%sdet1:%s" % (prefix, attr), attr)
        for i in range(nmcas):
            imca = i+1
            for j in range(8):
                isca = j+1
                attr = "C%iSCA%i"% (imca, isca)
                self.add_pv("%s%s:Value_RBV" % (prefix, attr), attr)
        poll(0.003, 0.25)
Esempio n. 14
0
    def __init__(self, prefix, filesaver='TIFF1:', fileroot='T:/xas_user'):

        attrs = ['%s%s' % (filesaver, p) for p in AD_FILE_ATTRS]
        Device.__init__(self, prefix, delim='', mutable=False, attrs=attrs)
        self.filesaver = filesaver
        self.fileroot = fileroot
        self._prefix = prefix
Esempio n. 15
0
    def __init__(self, prefix, roi=1, bgr_width=3, data_pv=None, with_poll=False):
        self._prefix = '%s:%i' % (prefix, roi)
        Device.__init__(self, self._prefix, delim=':',
                        attrs=('Name', 'MinX'), with_poll=with_poll)
        self._aliases = {'left': 'MinX',
                         'width': 'SizeX',
                         'name': 'Name',
                         'sum': 'Total_RBV',
                         'net': 'Net_RBV'}

        self.data_pv = data_pv
Esempio n. 16
0
    def __init__(self, prefix, roi=1, bgr_width=3, data_pv=None, with_poll=False):
        self._prefix = '%s:%i' % (prefix, roi)
        Device.__init__(self, self._prefix, delim=':',
                        attrs=('Name', 'MinX'), with_poll=with_poll)
        self._aliases = {'left': 'MinX',
                         'width': 'SizeX',
                         'name': 'Name',
                         'sum': 'Total_RBV',
                         'net': 'Net_RBV'}

        self.data_pv = data_pv
Esempio n. 17
0
    def __init__(self, prefix, nmca=4, filesaver='HDF5:',
                 fileroot='/home/xspress3/cars5/Data'):
        self.nmca = nmca
        attrs = list(self.attrs)
        attrs.extend(['%s%s' % (filesaver,p) for p in self.pathattrs])

        self.filesaver = filesaver
        self.fileroot = fileroot
        self._prefix = prefix

        Device.__init__(self, prefix, attrs=attrs, delim='')
        time.sleep(0.1)
Esempio n. 18
0
    def __init__(self, prefix, nmca=4, filesaver="HDF5:", fileroot="/home/xspress3/cars5/Data"):
        self.nmca = nmca
        attrs = list(self.attrs)
        attrs.extend(["%s%s" % (filesaver, p) for p in self.pathattrs])

        self.filesaver = filesaver
        self.fileroot = fileroot
        self._prefix = prefix
        self._save_rois = []
        self.mcas = [MCA(prefix, mca=i + 1) for i in range(nmca)]

        Device.__init__(self, prefix, attrs=attrs, delim="")
        time.sleep(0.1)
Esempio n. 19
0
    def set_prefix(self, prefix):
        if prefix.endswith(':'): prefix = prefix[:-1]
        if prefix.endswith(':image1'): prefix = prefix[:-7]
        if prefix.endswith(':cam1'): prefix = prefix[:-5]
        self.prefix = prefix

        self.ad_img = Device(prefix + ':image1:',
                             delim='',
                             attrs=self.img_attrs)
        self.ad_cam = Device(prefix + ':cam1:', delim='', attrs=self.cam_attrs)

        self.title.SetLabel("Epics AreaDetector: %s" % prefix)
        self.connect_pvs()
Esempio n. 20
0
    def __init__(self, prefix, nmca=4, filesaver='HDF5:',
                 fileroot='/home/xspress3/cars5/Data'):
        self.nmca = nmca
        attrs = list(self.attrs)
        attrs.extend(['%s%s' % (filesaver,p) for p in self.pathattrs])

        self.filesaver = filesaver
        self.fileroot = fileroot
        self._prefix = prefix
        self._save_rois = []
        self.mcas = [MCA(prefix, mca=i+1) for i in range(nmca)]

        Device.__init__(self, prefix, attrs=attrs, delim='')
        time.sleep(0.1)
Esempio n. 21
0
    def __init__(self, prefix, filesaver='netCDF1:',nmca=4,
                 fileroot='/home'):
        self.filesaver = filesaver
        self.fileroot = fileroot
        self._prefix = prefix
        self.nmca   = nmca

        self.dxps = [DXP(prefix, mca=i+1) for i in range(nmca)]
        self.mcas = [MCA(prefix, mca=i+1) for i in range(nmca)]

        Device.__init__(self, prefix, attrs=self.attrs,
                              delim='', mutable=True)
        for p in self.pathattrs:
            pvname = '%s%s%s' % (prefix, filesaver, p)
            self.add_pv(pvname, attr=p)
Esempio n. 22
0
    def set_prefix(self, prefix):
        if prefix.endswith(':'): prefix = prefix[:-1]
        if prefix.endswith(':image1'): prefix = prefix[:-7]
        if prefix.endswith(':cam1'): prefix = prefix[:-5]
        self.prefix = prefix

        self.ad_img = Device(prefix + ':image1:',
                             delim='',
                             attrs=self.img_attrs)
        self.ad_cam = Device(prefix + ':cam1:', delim='', attrs=self.cam_attrs)

        self.config_filesaver(prefix, self.format)

        w, h = self.GetImageSize()
        self.cam_name = prefix
Esempio n. 23
0
    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`
        
        Parameters
        ----------
        pvName : `string`
            Motor's base naming of the PV (Process Variable)
        mnemonic : `string`
            Motor's mnemonic
        """
        StandardDevice.__init__(self, mnemonic)
        self.pvName = pvName

        self.pvType = PV(pvName+".RTYP", connection_timeout=3)

        if(self.pvType.status == None):
            raise Exception("Epics PV "+ pvName+" seems to be offline or not reachable")

        if(self.pvType.get()!= "motor"):
            raise Exception(pvName+" is not an Epics Motor")
            

        self.motor = Device(pvName+'.', ('RBV','VAL', 'DRBV', 'DVAL','RLV', 'RVAL', 'RRBV',
                                         'STOP','MOVN','LLS','HLS','SSET','SUSE',
                                         'SET','VELO','EGU','DMOV','STUP', 'DESC',
                                         'BDST', 'HLM', 'LLM', 'DHLM', 'DLLM',
                                         'VOF','FOF','OFF', 'DIR','LVIO'))

        self.motor.add_callback('DMOV',self.onStatusChange)
        self._moving = self.isMovingPV()
        
        self.motorDesc = self.getDescription()
Esempio n. 24
0
    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        pvName : `string`
            Power supply base naming of the PV (Process Variable)
        mnemonic : `string`
            Temperature controller mnemonic
        """
        super().__init__(mnemonic)

        self.device = Device(pvName + ':', ['termopar', 'target', 'status', 'stepNum',
                             'programTable', 'programming', 'run', 'stop', 'advance',
                             'setPatternCount', 'timeScale', 'level1', 'reset', 'pause',
                             'sendCommand'])

        self.programmingDone = Event()
        self.newTemperature = Event()
        self.newStep = Event()
        self.device.add_callback('programming', self.onProgrammingChange)
        self.device.add_callback('termopar', self.onTemperatureChange)
        self.device.add_callback('stepNum', self.onStepChange)
        self.timeScaleCache = self.device.get('timeScale')

        self.pvName = pvName
        self.rate = 5
        self.presetDone = False
Esempio n. 25
0
    def __init__(self, pvName, mnemonic, timeBased=False):
        """
        **Constructor**
        To use this Keithley Class you must pass the PV (Process Variable) prefix.

            .. Note::
                e.g.: SXS:K6514

        Examples
        --------

        >>> from KeithleyClass import *
        >>> name = Keithley('SOL:K6514', 'k1')

        """
        StandardDevice.__init__(self, mnemonic)
        self.pvName = pvName
        self.timeBased = timeBased
        self.keithley = Device(pvName+':', 
                               ('GetMed','SetMed','GetMedRank','SetMedRank','GetAver',
                                'SetAver','GetAverCoun','SetAverCoun','GetNPLC','SetNPLC',
                                'GetAutoZero', 'SetAutoZero','GetZeroCheck','SetZeroCheck',
                                'GetAverTCon','SetAverTCon','GetCurrRange','SetCurrRange',
                                'GetZeroCor','SetZeroCor', 'GetAutoCurrRange','SetAutoCurrRange'
                                'Count','ContinuesMode', 'CNT', 'OneMeasure'))

        self.pvMeasure = PV(pvName+':'+'Measure', auto_monitor=False)
        self._counting = self.isCountingPV()
        self.keithley.add_callback('CNT', self.onStatusChange)
Esempio n. 26
0
class ConfPanel_Fly2AD(ConfPanel_Base):
    img_attrs = ('ArrayData', 'ArraySize0_RBV', 'ArraySize1_RBV',
                 'ArraySize2_RBV', 'ColorMode_RBV')

    def __init__(self,
                 parent,
                 image_panel=None,
                 prefix=None,
                 center_cb=None,
                 xhair_cb=None,
                 **kws):
        super(ConfPanel_Fly2AD, self).__init__(parent,
                                               center_cb=center_cb,
                                               xhair_cb=xhair_cb)

        sizer = self.sizer
        self.image_panel = image_panel
        self.SetBackgroundColour('#EEFFE')
        self.title = wx.StaticText(self,
                                   size=(285, 25),
                                   label="Fly2 Camera Mirror")
        labstyle = wx.ALIGN_LEFT | wx.EXPAND | wx.ALIGN_BOTTOM
        sizer.Add(self.title, (0, 0), (1, 3), labstyle)
        pack(self, sizer)
        self.set_prefix(prefix)

    @EpicsFunction
    def set_prefix(self, prefix):
        self.prefix = prefix
        self.ad_img = Device(prefix + ':image1:',
                             delim='',
                             attrs=self.img_attrs)
        self.title.SetLabel("Fly2AD: %s" % prefix)
        self.connect_pvs()

    @EpicsFunction
    def connect_pvs(self, verbose=True):
        if self.prefix is None or len(self.prefix) < 2:
            return

        time.sleep(0.025)
        if not self.ad_img.PV('ColorMode_RBV').connected:
            poll()
            if not self.ad_img.PV('ColorMode_RBV').connected:
                return
        poll()
Esempio n. 27
0
    def __init__(self, prefix, data_pv=None, nrois=None, roi_prefix=None):

        self._prefix = prefix
        Device.__init__(self, self._prefix, delim='',
                              attrs=self._attrs, with_poll=False)
        if data_pv is not None:
            self._pvs['VAL'] = PV(data_pv)
        self._npts = None
        self._nrois = nrois
        if self._nrois is None:
            self._nrois = MAX_ROIS

        self._roi_prefix = roi_prefix
        for i in range(self._nrois):
            p = get_pv('%s:%i:Name' % (self._roi_prefix, i+1))
            p = get_pv('%s:%i:MinX' % (self._roi_prefix, i+1))
            p = get_pv('%s:%i:SizeX' % (self._roi_prefix, i+1))
        self.get_rois()
        poll()
Esempio n. 28
0
    def __init__(self, prefix, data_pv=None, nrois=None, roi_prefix=None):

        self._prefix = prefix
        Device.__init__(self, self._prefix, delim='',
                              attrs=self._attrs, with_poll=False)
        if data_pv is not None:
            self._pvs['VAL'] = PV(data_pv)
        self._npts = None
        self._nrois = nrois
        if self._nrois is None:
            self._nrois = MAX_ROIS

        self._roi_prefix = roi_prefix
        for i in range(self._nrois):
            p = get_pv('%s:%i:Name' % (self._roi_prefix, i+1))
            p = get_pv('%s:%i:MinX' % (self._roi_prefix, i+1))
            p = get_pv('%s:%i:SizeX' % (self._roi_prefix, i+1))
        self.get_rois()
        poll()
Esempio n. 29
0
    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        pvName : `string`
            Power supply base naming of the PV (Process Variable)
        mnemonic : `string`
            Temperature controller mnemonic
        """
        super().__init__(mnemonic)

        self.device = Device(pvName + ':', ['PV:RBV', 'SP','RR', 'RR:RBV',
        'WSP:RBV', 'O' , 'O:RBV', 'MAN'])

        self.newTemp = Event()
        self.pvName = pvName
Esempio n. 30
0
    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        pvName : `string`
            Power supply base naming of the PV (Process Variable)
        mnemonic : `string`
            Temperature controller mnemonic
        """

        super().__init__(mnemonic)

        self.device = Device(pvName + ':', ['READ_TEMP_LOOP_HSET', 'READ_TEMP_LOOP_TSET','READ_TEMP_SIG_TEMP',
        'READ_RAMP_TEMP','READ_LEVEL_METER','READ_SAMPLE_FLOW','READ_SHIELD_FLOW',
        'SET_RAMP_TEMP', 'SET_TEMP_LOOP_TSET'])

        self.newTemp = Event()
        self.pvName = pvName
Esempio n. 31
0
    def __init__(self, prefix, scaler=None, nchan=8, clockrate=50.0):
        if not prefix.endswith(':'):
            prefix = "%s:" % prefix
        self._nchan = nchan
        self.scaler = None
        self.clockrate = clockrate # clock rate in MHz
        self._mode = SCALER_MODE

        if scaler is not None:
            self.scaler = Scaler(scaler, nchan=nchan)

        self.mcas = []
        for i in range(nchan):
            self.mcas.append(MCA(prefix, mca=i+1, nrois=2))

        Device.__init__(self, prefix, delim='',
                        attrs=self.attrs, mutable=False)

        time.sleep(0.05)
        for pvname, pv in self._pvs.items():
            pv.get()
Esempio n. 32
0
    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`
        
        Parameters
        ----------
        pvName : `string`
            Power supply base naming of the PV (Process Variable)
        mnemonic : `string`
            Power supply mnemonic
        """

        super().__init__(mnemonic)

        self.pvName = pvName
        self.voltage = Device(pvName + ':VOLTAGE:', self.RANGE)
        self.current = Device(pvName + ':CURRENT:', self.RANGE)
        self.program = Device(pvName + ':PROGRAM:', self.PROGRAM)
        self.programVoltage = Device(pvName + ':PROGRAM:VOLTAGE:', self.PROGRAM_SUB)
        self.programCurrent = Device(pvName + ':PROGRAM:CURRENT:', self.PROGRAM_SUB)
        self.resetPV = PV(pvName + ':RESET')
        self.mode = Device(pvName + ':MODE:', ['SET', 'GET', 'GET.PROC'])
        self.operationFlag = Device(pvName + ':',
                                    ['GET:OPERATION:FLAG', 'GET:OPERATION:FLAG.PROC'])
        self.timePV = PV(pvName + ':PROGRAM:TIME:ADD')
        self.error = Device(pvName + ':', ['ERROR', 'ERROR.PROC', 'ERROR:TEXT'])

        self.defaults()

        # Operation mode (voltage x current) is cached, so get it immediatelly
        self.procAndGet(self.mode, 'GET')        
Esempio n. 33
0
    def __init__(self, pvName, mnemonic, timeBased=False):
        """
        **Constructor**
        To use this Keithley Class you must pass the PV (Process Variable) prefix.

            .. Note::
                e.g.: SXS:K6514

        Examples
        --------

        >>> from KeithleyClass import *
        >>> name = Keithley('SOL:K6514', 'k1')

        """
        StandardDevice.__init__(self, mnemonic)
        self.pvName = pvName
        self.timeBased = timeBased
        self.keithley = Device(
            pvName + ":",
            (
                "GetMed",
                "SetMed",
                "GetMedRank",
                "SetMedRank",
                "GetAver",
                "SetAver",
                "GetAverCoun",
                "SetAverCoun",
                "GetNPLC",
                "SetNPLC",
                "GetAutoZero",
                "SetAutoZero",
                "GetZeroCheck",
                "SetZeroCheck",
                "GetAverTCon",
                "SetAverTCon",
                "GetRange",
                "SetRange",
                "GetZeroCor",
                "SetZeroCor",
                "GetAutoCurrRange",
                "SetAutoCurrRange" "Count",
                "ContinuesMode",
                "CNT",
                "OneMeasure",
            ),
        )

        self.pvMeasure = PV(pvName + ":" + "Measure", auto_monitor=False)
        self._counting = self.isCountingPV()
        self.keithley.add_callback("CNT", self.onStatusChange)
Esempio n. 34
0
    def __init__(self, prefix, scaler=None, nchan=8, clockrate=50.0):
        self._nchan = nchan
        self.scaler = None
        self.clockrate = clockrate # clock rate in MHz
        self._mode = SCALER_MODE

        if scaler is not None:
            self.scaler = Scaler(scaler, nchan=nchan)

        self.mcas = []
        for i in range(nchan):
            self.mcas.append(MCA(prefix, mca=i+1, nrois=2))

        Device.__init__(self, prefix, delim='',
                        attrs=self.attrs, mutable=False)

        time.sleep(0.05)
        for pvname, pv in self._pvs.items():
            pv.get()

        self.ast_interp = asteval.Interpreter()
        self.read_scaler_config()
Esempio n. 35
0
    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        pvName : `string`
            Power supply base naming of the PV (Process Variable)
        mnemonic : `string`
            Temperature controller mnemonic
        """
        super().__init__(mnemonic)
        self.pvName = pvName

        self.lauda = Device(pvName + ':', [
            'BLEVEL', 'BOVERTEMP', 'BPOWER', 'BSP', 'BSTATS', 'BTEMP', 'BTN',
            'BTHERMOSTATS', 'WSP', 'WSTART', 'ETEMP', 'WPUMP', 'WSTOP', 'WTN'
        ])
        self.newTemperature = Event()
        self.lauda.add_callback('BTEMP', self.onTemperatureChange)
        # Skip initial callback
        self.newTemperature.wait(1)
Esempio n. 36
0
    def __init__(self, mnemonic, pvName, axis):

        super().__init__(mnemonic)
        self.hexapode = Device(
            pvName + ':',
            ('STATE#PANEL:SET', 'STATE#PANEL:GET', 'STATE#PANEL:BUTTON',
             'MOVE#PARAM:CM', 'MOVE#PARAM:X', 'MOVE#PARAM:Y', 'MOVE#PARAM:Z',
             'MOVE#PARAM:RX', 'MOVE#PARAM:RY', 'MOVE#PARAM:RZ', ':POSUSER:X',
             ':POSUSER:Y', ':POSUSER:Z', ':POSUSER:RX', ':POSUSER:RY',
             ':POSUSER:RZ', ':POSMACH:X', ':POSMACH:Y', ':POSMACH:Z',
             ':POSMACH:RX', ':POSMACH:RY', ':POSMACH:RZ', 'CFG#CS:1',
             'CFG#CS:2', 'STATE#POSVALID?', 'CFG#CS?:1', 'CFG#CS?:2',
             'CFG#CS?:3', 'CFG#CS?:4', 'CFG#CS?:5', 'CFG#CS?:6', 'CFG#CS?:7',
             'CFG#CS?:8', 'CFG#CS?:9', 'CFG#CS?:10', 'CFG#CS?:11',
             'CFG#CS?:12', 'CFG#CS?:13'))
        self.axis = axis
        self.axis_dic = {"X": 1, "Y": 2, "Z": 3, "RX": 4, "RY": 5, "RZ": 6}
        self.axis_number = self.axis_dic[self.axis]
        self.rbv = PV(pvName + ':' + 'POSUSER:'******'POSUSER:'******'POSUSER:'******'POSUSER:' + self.axis + ".SCAN", 9)
Esempio n. 37
0
    def __init__(self, prefix, nmca=4, filesaver="HDF1:", fileroot="/home/xspress3/cars5/Data"):
        if not prefix.endswith(":"):
            prefix = "%s:" % prefix
        self.nmca = nmca
        attrs = []
        attrs.extend(["%s%s" % (filesaver, p) for p in self.pathattrs])

        self.filesaver = filesaver
        self.fileroot = fileroot
        self._prefix = prefix
        self._save_rois = []
        self.mcas = []
        for i in range(nmca):
            imca = i + 1
            dprefix = "%sdet1" % prefix
            rprefix = "%sMCA%iROI" % (prefix, imca)
            data_pv = "%sMCA%i:ArrayData" % (prefix, imca)
            mca = ADMCA(dprefix, data_pv=data_pv, roi_prefix=rprefix)
            self.mcas.append(mca)

        Device.__init__(self, prefix, attrs=attrs, delim="")
        for attr in self.det_attrs:
            self.add_pv("%sdet1:%s" % (prefix, attr), attr)
        time.sleep(0.1)
Esempio n. 38
0
    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        pvName : `string`
            Power supply base naming of the PV (Process Variable)
        mnemonic : `string`
            Temperature controller mnemonic
        """
        super().__init__(mnemonic)
        self.pvName = pvName

        self.linkam = Device(pvName + ':', ['setRate', 'setLimit', 'pending', 'temp',
                                            'stop', 'setSpeed', 'pumpMode', 'status',
                                            'start'])
        self.done = Event()
        self.newTemperature = Event()
        self.pending = bool(self.linkam.get('pending'))
        self.setRate(5)
        self.linkam.add_callback('pending', self.onPendingChange)
        self.linkam.add_callback('temp', self.onTemperatureChange)
Esempio n. 39
0
class Modem(StandardDevice):

    def onStatusChange(self, value, **kw):
        #print "Modem Status Now is: ", value
        self._status = value

    #CONSTRUCTOR OF MODEM CLASS
    def __init__(self, pvName, mnemonic):
        StandardDevice.__init__(self, mnemonic)
        self.pvName = pvName
        self.modem = Device(pvName+':FONE:', ('discar.PROC','audio','numero','discar.VALA'))
        self._status = self.getStatus()
        self.modem.add_callback('discar.VALA',self.onStatusChange)


    def getStatus(self):
        return self.modem.get('discar.VALA')

    def getDiscar(self):
        return self.modem.get('discar.PROC')

    def setDiscar(self, discar):
        self.setStatus("1 - Aguardando Instrucoes")
        sleep(0.5)
        self.modem.put('discar.PROC', discar)

    def setAudio(self, audio):
        self.modem.put('audio',audio)

    def setNumero(self, numero):
        self.modem.put('numero',numero)

    def setStatus(self, status):
        self.modem.put('discar.VALA',status)
    
    def getStatusCode(self):
        return int(self._status[:2])

    def waitCall(self):
        while self.getStatusCode() < 11:
            sleep(1)
Esempio n. 40
0
    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`
        
        Parameters
        ----------
        pvName : `string`
            Power supply base naming of the PV (Process Variable)
        mnemonic : `string`
            Power supply mnemonic
        """

        super().__init__(mnemonic)

        self.pvName = pvName
        self.voltage = Device(pvName + ':VOLTAGE:', self.RANGE)
        self.current = Device(pvName + ':CURRENT:', self.RANGE)
        self.program = Device(pvName + ':PROGRAM:', self.PROGRAM)
        self.programVoltage = Device(pvName + ':PROGRAM:VOLTAGE:',
                                     self.PROGRAM_SUB)
        self.programCurrent = Device(pvName + ':PROGRAM:CURRENT:',
                                     self.PROGRAM_SUB)
        self.resetPV = PV(pvName + ':RESET')
        self.mode = Device(pvName + ':MODE:', ['SET', 'GET', 'GET.PROC'])
        self.operationFlag = Device(
            pvName + ':', ['GET:OPERATION:FLAG', 'GET:OPERATION:FLAG.PROC'])
        self.timePV = PV(pvName + ':PROGRAM:TIME:ADD')
        self.error = Device(pvName + ':',
                            ['ERROR', 'ERROR.PROC', 'ERROR:TEXT'])

        self.defaults()

        # Operation mode (voltage x current) is cached, so get it immediatelly
        self.procAndGet(self.mode, 'GET')
Esempio n. 41
0
class KepcoBOP(IScannable, StandardDevice):
    """
    Class to control Kepco BOP GL power supplies via EPICS.

    Examples
    --------
    >>> from py4syn.epics.KepcoBOPClass import KepcoBOP
    >>>    
    >>> def configurePower(pv="", name="", voltage=5.0, currentLimit=1.0):
    ...    bop = KepcoBOP(pv, name)
    ...    bop.reset()
    ...    bop.setCurrentLimits(currentLimit, currentLimit)
    ...    bop.setVoltage(voltage)
    ...    return bop
    ...
    >>> def smoothVoltageTransition(bop, initial=0.0, final=12.0, duration=2.0):
    ...    bop.setRampWaveform(duration, final-initial, (initial+final)/2)
    ...    bop.waveformStart()
    ...    bop.waveformWait()
    ...
    >>> def noiseCurrent(bop):
    ...    bop.reset()
    ...    bop.setMode(KepcoBOP.MODE_CURRENT)
    ...    bop.setVoltageLimits(20, 20)
    ...    points = [random.uniform(-5, 5) for i in range(100)]
    ...    bop.clearWaveform()
    ...    bop.addWaveformPoints(points, [0.025])
    ...    bop.waveformStart()
    ...
    """

    MODE_VOLTAGE = 'VOLTAGE'
    MODE_CURRENT = 'CURRENT'
    MAX_POINTS_SINGLE_DWELL = 5900
    MAX_POINTS_FEW_DWELLS = 3933
    MAX_POINTS_MANY_DWELLS = 2950
    FEW_DWELLS_THRESHOLD = 126
    MAX_POINTS_PER_ADD = 21
    WAVEFORM_RUNNING_FLAG = 16384
    MAX_VOLTAGE = 50
    MAX_CURRENT = 20
    MIN_DWELL = 0.000093
    MAX_DWELL = 0.034

    RANGE = [
        'GET', 'GET.PROC', 'SET', 'SET:LIMIT:POSITIVE', 'SET:LIMIT:NEGATIVE',
        'SET:PROTECTION:POSITIVE', 'SET:PROTECTION:NEGATIVE',
        'GET:LIMIT:POSITIVE', 'GET:LIMIT:POSITIVE.PROC', 'GET:LIMIT:NEGATIVE',
        'GET:LIMIT:NEGATIVE.PROC', 'GET:PROTECTION:POSITIVE',
        'GET:PROTECTION:POSITIVE.PROC', 'GET:PROTECTION:NEGATIVE',
        'GET:PROTECTION:NEGATIVE.PROC'
    ]

    PROGRAM = [
        'WAVEFORM:TYPE', 'WAVEFORM:PERIOD', 'WAVEFORM:AMPLITUDE',
        'WAVEFORM:OFFSET', 'REPEAT', 'CLEAR', 'MARK:REPEAT'
    ]

    PROGRAM_SUB = [
        'ADD', 'WAVEFORM:ADD:2ARGUMENTS', 'WAVEFORM:ADD:3ARGUMENTS',
        'WAVEFORM:SET:ANGLE', 'WAVEFORM:START:ANGLE', 'WAVEFORM:STOP:ANGLE',
        'START', 'STOP', 'ABORT', 'POINTS', 'POINTS.PROC'
    ]

    WAVEFORM_PARAM1_LIMITS = {
        'SQUARE': (0.02, 1000),
        'RAMP+': (0.02, 532),
        'RAMP-': (0.02, 532),
        'SINE': (0.01, 443),
        'LEVEL': (0.0005, 5),
    }

    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`
        
        Parameters
        ----------
        pvName : `string`
            Power supply base naming of the PV (Process Variable)
        mnemonic : `string`
            Power supply mnemonic
        """

        super().__init__(mnemonic)

        self.pvName = pvName
        self.voltage = Device(pvName + ':VOLTAGE:', self.RANGE)
        self.current = Device(pvName + ':CURRENT:', self.RANGE)
        self.program = Device(pvName + ':PROGRAM:', self.PROGRAM)
        self.programVoltage = Device(pvName + ':PROGRAM:VOLTAGE:',
                                     self.PROGRAM_SUB)
        self.programCurrent = Device(pvName + ':PROGRAM:CURRENT:',
                                     self.PROGRAM_SUB)
        self.resetPV = PV(pvName + ':RESET')
        self.mode = Device(pvName + ':MODE:', ['SET', 'GET', 'GET.PROC'])
        self.operationFlag = Device(
            pvName + ':', ['GET:OPERATION:FLAG', 'GET:OPERATION:FLAG.PROC'])
        self.timePV = PV(pvName + ':PROGRAM:TIME:ADD')
        self.error = Device(pvName + ':',
                            ['ERROR', 'ERROR.PROC', 'ERROR:TEXT'])

        self.defaults()

        # Operation mode (voltage x current) is cached, so get it immediatelly
        self.procAndGet(self.mode, 'GET')

    def procAndGet(self, device, pv):
        """
        Helper method to synchronously execute a query in the device.
        """
        device.put(pv + '.PROC', 0, wait=True)
        return device.PV(pv).get(use_monitor=False)

    def getError(self):
        """
        Helper method to pop the last error from the device error queue.
        """
        error = self.procAndGet(self.error, 'ERROR')
        text = self.error.PV('ERROR:TEXT').get(use_monitor=False)
        return error, text

    def checkError(self):
        """
        Helper method to raise an exception if the device reports an error.
        """
        error, text = self.getError()
        if error != 0:
            raise RuntimeError('Device returned error: %d, %s' % (error, text))

    def defaults(self):
        """
        Helper method to reset internal data.
        """
        self.programPoints = 0
        self.programTimes = []
        self.blockStopCommand = False
        self.oneShotTime = 0

    def setVoltage(self, voltage):
        """
        Sets the voltage value. This method can only be used when in voltage mode.
        The voltage values must be within the power supply acceptable range and
        also within the configured limit values.

        See also: :meth:`setMode`, :meth:`setVoltageLimits`

        Parameters
        ----------
        voltage : `float`
            The desired voltage value
        """
        if self.cachedMode() != self.MODE_VOLTAGE:
            raise RuntimeError('Not in voltage mode')

        if abs(voltage) > self.MAX_VOLTAGE * 1.01:
            raise ValueError('Voltage out of range: %g' % voltage)

        self.voltage.put('SET', voltage, wait=True)

    def setCurrent(self, current):
        """
        Sets the current value. This method can only be used when in current mode.
        The current values must be within the power supply acceptable range and
        also within the configured limit values.

        See also: :meth:`setMode`, :meth:`setCurrentLimits`
        
        Parameters
        ----------
        current : `float`
            The desired current value
        """
        if self.cachedMode() != self.MODE_CURRENT:
            raise RuntimeError('Not in current mode')

        if abs(current) > self.MAX_CURRENT * 1.01:
            raise ValueError('Current out of range: %g' % current)

        self.current.put('SET', current, wait=True)

    def getVoltage(self):
        """
        Measures the voltage value. The measured value that is read back is known
        to have an error (less than 1%) and a delay (up to 320ms).

        Returns
        -------
        `float`
        """
        return self.procAndGet(self.voltage, 'GET')

    def getCurrent(self):
        """
        Measures the current value. The measured value that is read back is known
        to have an error (less than 1%) and a delay (up to 320ms).

        Returns
        -------
        `float`
        """
        return self.procAndGet(self.current, 'GET')

    def setMode(self, mode):
        """
        Changes the operating mode. Supported modes are voltage mode and current mode.
        In the voltage mode, the device tries to set a specific voltage, while staying
        within the current protection limits. In current mode, the device tries to set
        a specific current, while staying within the voltage protection limits.
        See also: :meth:`setCurrentLimits`, :meth:`setVoltageLimits`

        Parameters
        ----------
        mode : {KepcoBOP.MODE_CURRENT, KepcoBOP.MODE_VOLTAGE}
            The desired mode
        """
        self.mode.put('SET', mode, wait=True)
        # GET is used as the cached mode, so it needs to be updated too
        self.procAndGet(self.mode, 'GET')

    def cachedMode(self):
        """
        Helper method to return cached operating mode
        """
        return self.MODE_VOLTAGE if self.mode.get(
            'GET') == 0 else self.MODE_CURRENT

    def setLimits(self,
                  device,
                  mode,
                  negative=None,
                  positive=None,
                  maximum=1e100):
        """
        Helper method that implements :meth:`setVoltageLimits` and
        :meth:`setCurrentLimits`
        """
        pv = 'LIMIT' if self.cachedMode() == mode else 'PROTECTION'

        if not negative is None:
            if negative < 0:
                raise ValueError('Value must be absolute: %g' % negative)

            if negative > maximum:
                raise ValueError('Value out of range: %g (max: %g)' %
                                 (negative, maximum))

            device.put('SET:%s:NEGATIVE' % pv, negative, wait=True)
        if not positive is None:
            if positive < 0:
                raise ValueError('Value must be absolute: %g' % positive)

            if positive > maximum:
                raise ValueError('Value out of range: %g (max: %g)' %
                                 (positive, maximum))

            device.put('SET:%s:POSITIVE' % pv, positive, wait=True)

    def setVoltageLimits(self, negative=None, positive=None):
        """
        Sets the negative and positive voltage limits allowed for operation. The specific
        limit type that is set depends on the operating mode. When in voltage mode,
        the limit set is a main channel limit: it defines the limits of the voltages that
        can be programmed by the user. When in current mode, the limit is a protection
        limit: it defines the voltage limits that the load may impose because of the
        requested current. When changing the operating modes, the set limits no longer
        apply: they must be set again for the new mode.

        See also: :meth:`setMode`

        Parameters
        ----------
        negative : `float`
            The absolute value of the negative limit
        positive : `float`
            The absolute value of the positive limit
        """
        self.setLimits(self.voltage, self.MODE_VOLTAGE, negative, positive,
                       self.MAX_VOLTAGE)

    def setCurrentLimits(self, negative=None, positive=None):
        """
        Sets the negative and positive current limits allowed for operation. The specific
        limit type that is set depends on the operating mode. When in current mode,
        the limit set is a main channel limit: it defines the limits of the current that
        can be programmed by the user. When in voltage mode, the limit is a protection
        limit: it defines the current limits that the load may impose because of the
        requested voltage. When changing the operating modes, the set limits no longer
        apply: they must be set again for the new mode.

        See also: :meth:`setMode`

        Parameters
        ----------
        negative : `float`
            The absolute value of the negative limit
        positive : `float`
            The absolute value of the positive limit
        """
        self.setLimits(self.current, self.MODE_CURRENT, negative, positive,
                       self.MAX_CURRENT)

    def getLimits(self, device, mode):
        """
        Helper method that implements :meth:`getVoltageLimits` and :meth:
        `getCurrentLimits`
        """
        pv = 'LIMIT' if self.cachedMode() == mode else 'PROTECTION'

        negative = self.procAndGet(device, 'GET:%s:NEGATIVE' % pv)
        positive = self.procAndGet(device, 'GET:%s:POSITIVE' % pv)

        return negative, positive

    def getVoltageLimits(self):
        """
        Gets the negative and positive voltage limits allowed for operation. The specific
        limit type that is read depends on the operating mode. When in voltage mode,
        the limit set is a main channel limit: it defines the limits of the voltages that
        can be programmed by the user. When in current mode, the limit is a protection
        limit: it defines the voltage limits that the load may impose because of the
        requested current. When changing the operating modes, the set limits no longer
        apply: they are different for the new mode.

        See also: :meth:`setMode`

        Returns
        -------
        negative : `float`
            The absolute value of the negative limit
        positive : `float`
            The absolute value of the positive limit
        """
        return self.getLimits(self.voltage, self.MODE_VOLTAGE)

    def getCurrentLimits(self):
        """
        Gets the negative and positive current limits allowed for operation. The specific
        limit type that is read depends on the operating mode. When in current mode,
        the limit set is a main channel limit: it defines the limits of the current that
        can be programmed by the user. When in voltage mode, the limit is a protection
        limit: it defines the current limits that the load may impose because of the
        requested voltage. When changing the operating modes, the set limits no longer
        apply: they are different for the new mode.

        See also: :meth:`setMode`

        Returns
        -------
        negative : `float`
            The absolute value of the negative limit
        positive : `float`
            The absolute value of the positive limit
        """
        return self.getLimits(self.current, self.MODE_CURRENT)

    def reset(self):
        """
        Resets the device to a known state. Possible reset states may vary with the
        device configuration, but generally will be a zero value output (either voltage
        or current) with low protection limits. The device error queue will be cleared.
        """
        self.resetPV.put(0, wait=True)
        self.defaults()
        self.procAndGet(self.mode, 'GET')

    def clearWaveform(self):
        """
        Clears the programmed waveform data. This is required when building a new
        waveform program. Waveform data can only be cleared while the program is
        not running.
        """
        self.program.put('CLEAR', 0, wait=True)
        self.checkError()
        self.defaults()

    def getProgramLength(self):
        """
        Helper method that returns the number of points in current waveform program.
        """
        if self.cachedMode() == self.MODE_VOLTAGE:
            device = self.programVoltage
        else:
            device = self.programCurrent

        p = self.procAndGet(device, 'POINTS')
        self.checkError()
        return p

    def addWaveformPoints(self, points, times):
        """
        Adds a set of points to the waveform program. This is one of the ways that the
        device can be programmed to generate a complex waveform. It's the most flexible
        way, allowing arbitrary waveforms, but it's also the slowest one (a 10 second
        waveform may take at least 4 seconds just to upload the program). This method
        adds more points to the current waveform program. It does not overwrite the
        existing program.
        
        Adding points does not execute the program. The method :meth:`waveformStart` must
        be called.

        See also: :meth:`setWaveformPoints`, :meth:`addSineWaveform`,
        :meth:`addTriangleWaveform`, :meth:`addRampWaveform`, :meth:`addSquareWaveform`,
        :meth:`addLevelWaveform`, :meth:`waveformStart`

        .. note::
            When changing operating mode, the waveform program must be cleared if
            necessary, because the device will prohibit mixing points from different
            modes.

        Parameters
        ----------
        points : `array of floats`
            The array of points to be added to the program. The total number of allowed
            points may vary, depending on the times array. When the times array has
            exactly one element, the maximum number of points is 5900. When the
            times array has at most 126 distinct values, the maximum number of points
            is 3933. When the times array has more than 126 distinct values, the
            maximum number of points is 2950.
        times : `array of floats`
            The dwell times for each point. Either there must be one time entry for
            each point, or the array of times must contain exactly one element, which
            sets the time for all points. The allowed time range is [93e-6, 34e-3]
            (93µs to 34ms).
        """
        x = min(times)
        if x < self.MIN_DWELL:
            raise ValueError('Minimum time out of range: %g (min: %g)' %
                             (x, self.MIN_DWELL))

        x = max(times)
        if x > self.MAX_DWELL:
            raise ValueError('Maximum time out of range: %g (max: %g)' %
                             (x, self.MAX_DWELL))

        p = self.programPoints + len(points)
        t = self.programTimes + times
        distinct = len(set(t))

        if distinct > self.FEW_DWELLS_THRESHOLD:
            maxPoints = self.MAX_POINTS_MANY_DWELLS
        elif distinct > 1:
            maxPoints = self.MAX_POINTS_FEW_DWELLS
        else:
            maxPoints = self.MAX_POINTS_SINGLE_DWELL

        if p > maxPoints:
            raise ValueError(
                'Requested waveform too large: %u (maximum is: %u)' %
                (p, maxPoints))

        if self.cachedMode() == self.MODE_VOLTAGE:
            device = self.programVoltage
        else:
            device = self.programCurrent

        for i in range(0, len(points), self.MAX_POINTS_PER_ADD):
            l = array(points[i:i + self.MAX_POINTS_PER_ADD])
            device.put('ADD', l, wait=True)

        for i in range(0, len(times), self.MAX_POINTS_PER_ADD):
            l = array(times[i:i + self.MAX_POINTS_PER_ADD])
            self.timePV.put(l, wait=True)

        self.programPoints = p
        self.programTimes = t

    def setWaveformPoints(self, points, times):
        """
        A shortcut to clearing the waveform program, adding waveform points and setting
        the repeat count to 1.

        See also: :meth:`clearWaveform`, :meth:`addWaveformPoints`,
        :meth:`setWaveformRepeat`

        Parameters
        ----------
        points : `array of floats`
            Parameter passed to :meth:`addWaveformPoints`
        times : `array of floats`
            Parameter passed to :meth:`addWaveformPoints`
        """
        self.clearWaveform()
        self.addWaveformPoints(points, times)
        self.setWaveformRepeat(1)
        self.checkError()

    def setWaveformAngle(self, start=0, stop=360):
        """
        Helper method that configures start and stop angles for sine and triangle
        waveforms.
        """
        if start < 0 or start > 359.99:
            raise ValueError('Start angle must be between 0 and 359.99')

        if stop < 0.01 or stop > 360:
            raise ValueError('Stop angle must be between 0.01 and 360')

        if self.cachedMode() == self.MODE_VOLTAGE:
            device = self.programVoltage
        else:
            device = self.programCurrent

        if device.get('WAVEFORM:START:ANGLE') != start:
            device.put('WAVEFORM:START:ANGLE', start, wait=True)

        if device.get('WAVEFORM:STOP:ANGLE') != stop:
            device.put('WAVEFORM:STOP:ANGLE', stop, wait=True)

        device.put('WAVEFORM:SET:ANGLE', 0, wait=True)
        self.checkError()

    def addWaveform(self, type, param1, param2, param3=None):
        """
        Helper method that implements adding waveform segments.
        """
        if type not in self.WAVEFORM_PARAM1_LIMITS:
            raise ValueError('Invalid waveform type: %s' % type)

        x, y = self.WAVEFORM_PARAM1_LIMITS[type]
        if param1 < x or param1 > y:
            raise ValueError('Frequency or period parameter out of range: %g '
                             '(interval: [%g, %g])' % (param1, x, y))

        if self.cachedMode() == self.MODE_VOLTAGE:
            device = self.programVoltage
            maxValue = self.MAX_VOLTAGE
        else:
            device = self.programCurrent
            maxValue = self.MAX_CURRENT

        if param2 < 0 or param2 > 2 * maxValue:
            raise ValueError('Amplitude out of range: %g (range: [%g, %g])' %
                             (param2, 0, 2 * maxValue))

        if param3 is not None and abs(param3) > maxValue:
            raise ValueError('Offset out of range: %g (range: [%g, %g])' %
                             (param3, -maxValue, maxValue))

        self.program.put('WAVEFORM:TYPE', type, wait=True)
        self.program.put('WAVEFORM:PERIODORFREQUENCY', param1, wait=True)
        self.program.put('WAVEFORM:AMPLITUDE', param2, wait=True)

        if param3 is not None:
            self.program.put('WAVEFORM:OFFSET', param3, wait=True)
            device.put('WAVEFORM:ADD:3ARGUMENTS', 0, wait=True)
        else:
            device.put('WAVEFORM:ADD:2ARGUMENTS', 0, wait=True)

        self.checkError()
        l = self.getProgramLength()
        self.programPoints = l
        # Fake distinct dwell time for waveform
        self.programTimes += [0]

    def addSineWaveform(self, frequency, amplitude, offset, start=0, stop=360):
        """
        Adds a sine wave to the waveform program. This is one of the ways that the
        device can be programmed to generate a complex waveform. The other way is
        sending the complete array of points for the waveform. This method is limited
        to a specific waveform type and it also consumes a lot of points, but it's
        faster to program than uploading individual points. When the final desired
        waveform can be composed of simple waveform segments, this is the best way
        to program the device.

        Adding points does not execute the program. The method :meth:`waveformStart` must
        be called.

        See also: :meth:`waveformStart`, :meth:`setSineWaveform`,
        :meth:`addWaveformPoints`, :meth:`addTriangleWaveform`, :meth:`addRampWaveform`,
        :meth:`addSquareWaveform`, :meth:`addLevelWaveform`

        .. note::
            When changing operating mode, the waveform program must be cleared if
            necessary, because the device will prohibit mixing points from different
            modes.

        Parameters
        ----------
        frequency : `float`
            The sine wave frequency. The allowed range is 0.01Hz to 443Hz. The number
            of points used vary from 3840, for lower frequency waves to 24, for
            higher frequency waves.
        amplitude : `float`
            The sine wave peak to peak amplitude. The peak to peak amplitude cannot
            exceed the range defined by the configured operating device limits.
        offset : `float`
            The offset of the sine wave zero amplitude. The offset cannot exceed the
            configured device limits.
        start : `float`
            The starting angle for the sine wave, in degrees. Allowed range is
            [0.0, 359.99]
        stop : `float`
            The stop angle for the sine wave, in degrees. Allowed range is [0.01, 360.0]
        """
        self.setWaveformAngle(start, stop)
        self.addWaveform('SINE', frequency, amplitude, offset)

    def setSineWaveform(self, frequency, amplitude, offset, start=0, stop=360):
        """
        A shortcut to clearing the waveform program, adding sine waveform and setting
        the repeat count to 1.

        See also: :meth:`clearWaveform`, :meth:`addSineWaveform`,
        :meth:`setWaveformRepeat`

        Parameters
        ----------
        frequency : `float`
            Parameter passed to :meth:`addSineWaveform`
        amplitude : `float`
            Parameter passed to :meth:`addSineWaveform`
        offset : `float`
            Parameter passed to :meth:`addSineWaveform`
        start : `float`
            Parameter passed to :meth:`addSineWaveform`
        stop : `float`
            Parameter passed to :meth:`addSineWaveform`
        """
        self.clearWaveform()
        self.addSineWaveform(frequency, amplitude, offset, start, stop)
        self.setWaveformRepeat(1)

    def addTriangleWaveform(self,
                            frequency,
                            amplitude,
                            offset,
                            start=0,
                            stop=360):
        """
        Adds a triangle wave to the waveform program. This is one of the ways that the
        device can be programmed to generate a complex waveform. The other way is
        sending the complete array of points for the waveform. This method is limited
        to a specific waveform type and it also consumes a lot of points, but it's
        faster to program than uploading individual points. When the final desired
        waveform can be composed of simple waveform segments, this is the best way
        to program the device.

        Adding points does not execute the program. The method :meth:`waveformStart` must
        be called.

        See also: :meth:`waveformStart`, :meth:`setTriangleWaveform`,
        :meth:`addWaveformPoints`, :meth:`addSineWaveform`, :meth:`addRampWaveform`,
        :meth:`addSquareWaveform`, :meth:`addLevelWaveform`

        .. note::
            When changing operating mode, the waveform program must be cleared if
            necessary, because the device will prohibit mixing points from different
            modes.

        Parameters
        ----------
        frequency : `float`
            The triangle wave frequency. The allowed range is 0.01Hz to 443Hz. The number
            of points used vary from 3840, for lower frequency waves to 24, for
            higher frequency waves.
        amplitude : `float`
            The triangle wave peak to peak amplitude. The peak to peak amplitude cannot
            exceed the range defined by the configured operating device limits.
        offset : `float`
            The offset of the triangle wave zero amplitude. The offset cannot exceed the
            configured device limits.
        start : `float`
            The starting angle for the triangle wave, in degrees. Allowed range is
            [0.0, 359.99]
        stop : `float`
            The stop angle for the triangle wave, in degrees. Allowed range is
            [0.01, 360.0]
        """
        self.setWaveformAngle(start, stop)
        self.addWaveform('TRIANGLE', frequency, amplitude, offset)

    def setTriangleWaveform(self,
                            frequency,
                            amplitude,
                            offset,
                            start=0,
                            stop=360):
        """
        A shortcut to clearing the waveform program, adding triangle waveform and setting
        the repeat count to 1.

        See :meth:`clearWaveform`, :meth:`addTriangleWaveform`, :meth:`setWaveformRepeat`

        Parameters
        ----------
        frequency : `float`
            Parameter passed to :meth:`addTriangleWaveform`
        amplitude : `float`
            Parameter passed to :meth:`addTriangleWaveform`
        offset : `float`
            Parameter passed to :meth:`addTriangleWaveform`
        start : `float`
            Parameter passed to :meth:`addTriangleWaveform`
        stop : `float`
            Parameter passed to :meth:`addTriangleWaveform`
        """
        self.clearWaveform()
        self.addTriangleWaveform(frequency, amplitude, offset, start, stop)
        self.setWaveformRepeat(1)

    def addRampWaveform(self, length, height, offset):
        """
        Adds a ramp wave to the waveform program. This is one of the ways that the
        device can be programmed to generate a complex waveform. The other way is
        sending the complete array of points for the waveform. This method is limited
        to a specific waveform type and it also consumes a lot of points, but it's
        faster to program than uploading individual points. When the final desired
        waveform can be composed of simple waveform segments, this is the best way
        to program the device.

        Adding points does not execute the program. The method :meth:`waveformStart` must
        be called.

        See also: :meth:`waveformStart`, :meth:`setRampWaveform`,
        :meth:`addWaveformPoints`, :meth:`addSineWaveform`, :meth:`addTriangleWaveform`,
        :meth:`addSquareWaveform`, :meth:`addLevelWaveform`

        .. note::
            When changing operating mode, the waveform program must be cleared if
            necessary, because the device will prohibit mixing points from different
            modes.

        Parameters
        ----------
        length : `float`
            The ramp length. The allowed range is [1/532, 100] (1.88ms to 100s).
            The number of points used vary from 20, for smaller ramps, to 3840, for
            larger ramps.
        height : `float`
            The ramp height. The height can be positive or negative. It cannot
            exceed the range defined by the configured operating device limits.
        offset : `float`
            The offset of the ramp middle height. The offset cannot exceed the
            configured device limits.
        """
        if height >= 0:
            type = 'RAMP+'
        else:
            type = 'RAMP-'
            height = -height

        self.addWaveform(type, 1.0 / length, height, offset)

    def setRampWaveform(self, length, height, offset):
        """
        A shortcut to clearing the waveform program, adding ramp waveform and setting
        the repeat count to 1.

        See also: :meth:`clearWaveform`, :meth:`addRampWaveform`,
        :meth:`setWaveformRepeat`

        Parameters
        ----------
        length : `float`
            Parameter passed to :meth:`addRampWaveform`
        height : `float`
            Parameter passed to :meth:`addRampWaveform`
        offset : `float`
            Parameter passed to :meth:`addRampWaveform`
        """
        self.clearWaveform()
        self.addRampWaveform(length, height, offset)
        self.setWaveformRepeat(1)

    def addSquareWaveform(self, frequency, amplitude, offset):
        """
        Adds a square wave (constant 50% duty cycle, starts with positive excursion)
        to the waveform program. This is one of the ways that the device can be
        programmed to generate a complex waveform. The other way is sending the complete
        array of points for the waveform. This method is limited to a specific waveform
        type and it also consumes a lot of points, but it's faster to program than
        uploading individual points. When the final desired waveform can be composed
        of simple waveform segments, this is the best way to program the device.

        Adding points does not execute the program. The method :meth:`waveformStart` must
        be called.

        See also: :meth:`waveformStart`, :meth:`setSquareWaveform`,
        :meth:`addWaveformPoints`, :meth:`addSineWaveform`, :meth:`addTriangleWaveform`,
        :meth:`addRampWaveform`, :meth:`addLevelWaveform`

        .. note::
            When changing operating mode, the waveform program must be cleared if
            necessary, because the device will prohibit mixing points from different
            modes.

        Parameters
        ----------
        frequency : `float`
            The square wave frequency. The allowed range is 0.02Hz to 1000Hz. The number
            of points used vary from 3840, for lower frequency waves to 10, for
            higher frequency waves.
        amplitude : `float`
            The square wave peak to peak amplitude. The peak to peak amplitude cannot
            exceed the range defined by the configured operating device limits.
        offset : `float`
            The offset of the square wave zero amplitude. The offset cannot exceed the
            configured device limits.
        """
        self.addWaveform('SQUARE', frequency, amplitude, offset)

    def setSquareWaveform(self, frequency, amplitude, offset):
        """
        A shortcut to clearing the waveform program, adding square waveform and setting
        the repeat count to 1.

        See also: :meth:`clearWaveform`, :meth:`addSquareWaveform`, :meth:`setWaveformRepeat`

        Parameters
        ----------
        frequency : `float`
            Parameter passed to :meth:`addSquareWaveform`
        amplitude : `float`
            Parameter passed to :meth:`addSquareWaveform`
        offset : `float`
            Parameter passed to :meth:`addSquareWaveform`
        """
        self.clearWaveform()
        self.addSquareWaveform(frequency, amplitude, offset)
        self.setWaveformRepeat(1)

    def addLevelWaveform(self, length, offset):
        """
        Adds a fixed level wave to the waveform program. This is one of the ways that the
        device can be programmed to generate a complex waveform. The other way is
        sending the complete array of points for the waveform. This method is limited
        to a specific waveform type, but it's faster to program than uploading
        individual points. When the final desired waveform can be composed of simple
        waveform segments, this is the best way to program the device.

        Adding points does not execute the program. The method :meth:`waveformStart` must
        be called.

        See also: :meth:`waveformStart`, :meth:`setLevelWaveform`,
        :meth:`addWaveformPoints`, :meth:`addSineWaveform`, :meth:`addRampWaveform`,
        :meth:`addTriangleWaveform`, :meth:`addSquareWaveform`

        .. note::
            When changing operating mode, the waveform program must be cleared if
            necessary, because the device will prohibit mixing points from different
            modes.

        Parameters
        ----------
        length : `float`
            The duration of the level waveform. The allowed range is 500µs to 5s. The
            number of points used is 60.
        offset : `float`
            The level offset. The offset cannot exceed the configured device limits.
        """
        self.addWaveform('LEVEL', length, offset)

    def setLevelWaveform(self, length, offset):
        """
        A shortcut to clearing the waveform program, adding a level waveform and setting
        the repeat count to 1.

        See also: :meth:`clearWaveform`, :meth:`addLevelWaveform`,
        :meth:`setWaveformRepeat`

        Parameters
        ----------
        length : `float`
            Parameter passed to :meth:`addLevelWaveform`
        offset : `float`
            Parameter passed to :meth:`addLevelWaveform`
        """
        self.clearWaveform()
        self.addLevelWaveform(length, offset)
        self.setWaveformRepeat(1)

    def setWaveformRepeat(self, repeat):
        """
        Set the number of times the waveform program will run. By default, the program
        runs an indeterminate number of times, until it's explicitly stopped. This method
        is used to specify the number of times the waveform program will repeat.

        A waveform may also have separate one-shot part and repeatable part. Use the
        method :meth:`setWaveformRepeatMark` to separate them.

        See also: :meth:`setWaveformRepeatMark`, :meth:`waveformStop`,
        :meth:`waveformAbort`

        Parameters
        ----------
        repeat : `int`
            Number of times the programmed waveform will run. A value of zero means
            run until explicitly stopped.
        """
        if repeat < 0:
            raise ValueError('Negative repeat value: %d' % repeat)

        self.program.put('REPEAT', repeat, wait=True)
        self.checkError()

        # It's not reliable to call waveformStop with a finite repeat count
        # because calling immediatelly after the waveform program stops results
        # in an error and there's no atomic way to check and disable it,
        # so we just disallow using the stop command with finite repeat counts
        self.blockStopCommand = repeat > 0

    def getWaveformRepeat(self):
        """
        Gets the last requested waveform repeat count.

        See also: :meth:`setWaveformRepeat`

        Returns
        -------
        `int`
        """
        return self.program.get('REPEAT')

    def setWaveformRepeatMark(self, position=None):
        """
        Separates the one-short part from the repeating part in the waveform program. By
        default, the whole waveform repeats, according to the repeat count. This method
        marks the separation point that allows the definition of an initial one-shot
        part of the wave. The part before the marked point will be the one-shot part
        and after the marked point will be the repeating part.

        See also: :meth:`setWaveformRepeat`

        Parameters
        ----------
        position : `int`
            Desired position of the setWaveformRepeatMark, representing the point in
            the waveform that starts the repeating part. If unset, the current first
            free position in the waveform is set as the mark.
        """
        if position is None:
            position = self.getProgramLength()
        elif position < 0:
            raise ValueError('Negative position: %d' % position)

        self.program.put('MARK:REPEAT', position, wait=True)
        self.checkError()

    def waveformStart(self):
        """
        Executes a waveform program. The program must be already defined by using the
        waveform add methods. This methods triggers the execution and returns
        immediatelly. It does not wait for the complete waveform execution to finish.
        
        By default, the waveform will repeat until it is explicitly stopped, but this
        can be configured by the :meth:`setWaveformRepeat` method. To stop the
        waveform execution, the methods :meth:`waveformStop` and
        :meth:`waveformAbort` can be used. For a program with finite repeat count,
        it's possible to wait until the waveform finishes with :meth:`waveformWait`.

        See also: :meth:`addWaveformPoints`, :meth:`addSineWaveform`,
        :meth:`addTriangleWaveform`, :meth:`addRampWaveform`, :meth:`addSquareWaveform`,
        :meth:`addLevelWaveform`, :meth:`waveformStop`, :meth:`waveformAbort`,
        :meth:`waveformWait`, :meth:`isWaveformRunning`
        """
        if self.cachedMode() == self.MODE_VOLTAGE:
            self.programVoltage.put('START', 0, wait=True)
        else:
            self.programCurrent.put('START', 0, wait=True)

        self.checkError()

    def waveformStop(self):
        """
        Requests to stop a running waveform. The waveform will execute until the end
        and then will stop, without repeating the program again. The final output value
        will be the final point in the program.

        See also: :meth:`waveformAbort`, :meth:`waveformWait`

        .. note::
            Because it's not possible to reliably stop a program with finite repeat
            count without potentially triggering an "already finished" error, this
            command is only enabled for stopping waveform programs with inifinite
            repeat count. For finite repeat counts, use :meth:`waveformAbort`, or
            :meth:`waveformWait` instead.
        """
        if self.blockStopCommand:
            raise RuntimeError(
                'Cannot use stop command with finite repeat counts')

        if self.cachedMode() == self.MODE_VOLTAGE:
            self.programVoltage.put('STOP', 0, wait=True)
        else:
            self.programCurrent.put('STOP', 0, wait=True)

        self.checkError()

    def waveformAbort(self):
        """
        Immediatelly stops a running waveform. The final output value will be the value
        before running the waveform program.

        See also: :meth:`waveformStop`, :meth:`waveformWait`
        """
        if self.cachedMode() == self.MODE_VOLTAGE:
            self.programVoltage.put('ABORT', 0, wait=True)
        else:
            self.programCurrent.put('ABORT', 0, wait=True)

        self.checkError()

    def getOperationFlag(self):
        """
        Returns the real time value of the device operation condition register.
        The register contains a set of bits representing the following flags:
        "list running" (16384), "list complete" (4096), "sample complete" (2048),
        "constant current mode" (1024), "transient complete" (512)", "constant voltage
        mode" (256), "transient armed" (64), "waiting for trigger" (32). Refer to the
        Kepco BOP GL manual for specific details. The relevant flag used by the library
        is the "list running" flag, which indicates that theres a waveform program
        running.

        See also: :meth:`isWaveformRunning`, :meth:`waveformWait`

        Returns
        -------
            `int`
        """
        return self.procAndGet(self.operationFlag, 'GET:OPERATION:FLAG')

    def isWaveformRunning(self):
        """
        Returns whether there's a running waveform program.

        See also: :meth:`getOperationFlag`, :meth:`waveformWait`
        
        Returns
        -------
            `bool`
        """
        return bool(self.getOperationFlag() & self.WAVEFORM_RUNNING_FLAG)

    def waveformWait(self):
        """
        Waits until the whole waveform program finishes, including all repetitions.
        It's only possible to wait for waveform programs with finite repeat counts.

        .. note::
            When using the Kepco power supply with a serial port, it's not possible to
            receive a notification from the device when the waveform finishes, so this
            method works by repeatedly polling the device requesting the operation flag.
            Because of this, the recommended way to use this method is first sleeping
            for as much time as possible to avoid the loop and only on the last second
            call this method. Example of a helper function that accomplishes this:
            
            Examples
            --------
            >>> def runAndWait(bop, totalTime):
            ...     bop.waveformStart()
            ...     sleep(max(totalTime-1, 0))
            ...     bop.waveformWait()
            ...
        """
        while self.isWaveformRunning():
            poll(1e-2)

    def getValue(self):
        """
        Returns either the readback voltage or the current, depending on operating mode.

        Returns
        -------
            `float`
        """
        if self.cachedMode() == self.MODE_VOLTAGE:
            return self.getVoltage()

        return self.getCurrent()

    def setValue(self, v):
        """
        Sets either the current voltage or current, depending on operating mode.

        Parameters
        ----------
        v : `float`
            Either voltage, or current to set, depending on operating mode.
        """
        if self.cachedMode() == self.MODE_VOLTAGE:
            self.setVoltage(v)
        else:
            self.setCurrent(v)

    def wait(self):
        """
        Does the same as :meth:`waveformWait`.
        """
        self.waveformWait()

    def getLowLimitValue(self):
        """
        Gets either the voltage or current low limit value, depending on operation mode.

        Returns
        -------
            `float`
        """
        if self.cachedMode() == self.MODE_VOLTAGE:
            return -self.getLimits(self.voltage, self.MODE_VOLTAGE)[0]

        return -self.getLimits(self.current, self.MODE_CURRENT)[0]

    def getHighLimitValue(self):
        """
        Gets either the voltage or current high limit value, depending on operation mode.

        Returns
        -------
            `float`
        """

        if self.cachedMode() == self.MODE_VOLTAGE:
            return self.getLimits(self.voltage, self.MODE_VOLTAGE)[1]

        return self.getLimits(self.current, self.MODE_CURRENT)[1]
Esempio n. 42
0
class ADMonoImagePanel(wx.Panel):
    """Image Panel for monochromatic Area Detector"""

    ad_attrs = ('image1:ArrayData',
                'image1:ArraySize0_RBV',
                'image1:ArraySize1_RBV',
                'cam1:ArrayCounter_RBV')

    def __init__(self, parent, prefix=None, writer=None, draw_objects=None,
                 rot90=0, contrast_level=0, size=(600, 600), **kws):

        super(ADMonoImagePanel, self).__init__(parent, -1, size=size)

        self.drawing = False
        self.adcam = None
        self.image_id = -1

        self.writer = writer
        self.scale = 0.8
        self.colormap = None
        self.contrast_levels = [contrast_level, 100.0-contrast_level]
        self.rot90 = rot90
        self.flipv = False
        self.fliph = False
        self.image = None
        self.draw_objects = None
        self.SetBackgroundColour("#E4E4E4")
        self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
        self.Bind(wx.EVT_SIZE, self.onSize)
        self.Bind(wx.EVT_PAINT, self.onPaint)
        self.connect_pvs(prefix)
        self.restart_fps_counter()

    def restart_fps_counter(self, nsamples=100):
        self.capture_times = deque([], maxlen=nsamples)
        if self.writer is not None:
            self.writer("")

    def connect_pvs(self, prefix):
        prefix = fix_ad_prefix(prefix)
        self.adcam = Device(prefix,  delim='', attrs=self.ad_attrs)
        self.adcam.add_callback('cam1:ArrayCounter_RBV', self.onNewImage)

    def GetImageSize(self):
        return  (self.adcam.get('image1:ArraySize0_RBV'),
                 self.adcam.get('image1:ArraySize1_RBV'))

    @DelayedEpicsCallback
    def onNewImage(self, pvname=None, value=None, **kws):
        if value > self.image_id and not self.drawing:
            self.drawing = True
            self.image_id = value
            self.Refresh()
            self.drawing = False

    def GrabNumpyImage(self):
        """get raw image data, as numpy ndarray, correctly shaped"""
        try:
            data = self.adcam.PV('image1:ArrayData').get()
        except:
            data = None
        if data is not None:
            w, h = self.GetImageSize()
            data = data.reshape((h, w))
        poll()
        return data

    def GrabWxImage(self):
        """get wx Image:
        - scaled in size
        - color table applied
        - flipped and/or rotated
        - contrast levels set
        """
        data = self.GrabNumpyImage()
        if data is None:
            return
        self.capture_times.append(time.time())

        jmin, jmax = np.percentile(data, self.contrast_levels)
        data = (np.clip(data, jmin, jmax) - jmin)/(jmax+0.001)
        w, h = self.GetImageSize()

        if callable(self.colormap):
            data = self.colormap(data)
            if data.shape[2] == 4: # with alpha channel
                image = wx.Image(w, h,
                                 (data[:,:,:3]*255.).astype('uint8'),
                                 (data[:,:,3]*255.).astype('uint8'))
            else:
                image = wx.Image(w, h, (data*255.).astype('uint8'))
        else:
            rgb = np.zeros((h, w, 3), dtype='float')
            rgb[:, :, 0] = rgb[:, :, 1] = rgb[:, :, 2] = data
            image = wx.Image(w, h, (rgb*255.).astype('uint8'))

        if self.flipv:
            image = image.Mirror(False)
        if self.fliph:
            image = image.Mirror(True)
        if self.rot90 != 0:
            for i in range(self.rot90):
                image = image.Rotate90(True)
                w, h = h, w
        return image.Scale(int(self.scale*w), int(self.scale*h))

    def onSize(self, evt=None):
        if evt is not None:
            fh, fw = evt.GetSize()
        else:
            fh, fw = self.GetSize()

        w, h = self.GetImageSize()
        self.scale = max(0.10, min(fw/(w+5.0), fh/(h+5.0)))

    def onPaint(self, event):
        image = self.GrabWxImage()
        if image is not None:
            if len(self.capture_times) > 2 and self.writer is not None:
                ct = self.capture_times
                fps = (len(ct)-1) / (ct[-1]-ct[0])
                self.writer("Image %d: %.1f fps" % (self.image_id, fps))
            bitmap = wx.Bitmap(image)
            bmp_w, bmp_h = bitmap.GetSize()
            pan_w, pan_h = self.GetSize()
            pad_w, pad_h = int(1+(pan_w-bmp_w)/2.0), int(1+(pan_h-bmp_h)/2.0)
            dc = wx.AutoBufferedPaintDC(self)
            dc.Clear()
            dc.DrawBitmap(bitmap, pad_w, pad_h, useMask=True)

            # self.__draw_objects(dc, img_w, img_h, pad_w, pad_h)

    def __draw_objects(self, dc, img_w, img_h, pad_w, pad_h):
        dc.SetBrush(wx.Brush('Black', wx.BRUSHSTYLE_TRANSPARENT))
        if self.draw_objects is not None:
            for obj in self.draw_objects:
                shape = obj.get('shape', None)
                color = obj.get('color', None)
                if color is None:
                    color = obj.get('colour', 'Black')
                color = wx.Colour(*color)
                width = obj.get('width', 1.0)
                style = obj.get('style', wx.SOLID)
                args  = obj.get('args', [0, 0, 0, 0])
                kws   = obj.get('kws', {})

                method = getattr(dc, 'Draw%s' % (shape.title()), None)
                if shape.title() == 'Line':
                    margs = [pad_w + args[0]*img_w,
                             pad_h + args[1]*img_h,
                             pad_w + args[2]*img_w,
                             pad_h + args[3]*img_h]
                elif shape.title() == 'Circle':
                    margs = [pad_w + args[0]*img_w,
                             pad_h + args[1]*img_h,  args[2]*img_w]

                if method is not None:
                    dc.SetPen(wx.Pen(color, width, style))
                    method(*margs, **kws)
Esempio n. 43
0
class Eurotherm2408(StandardDevice, IScannable):
    """
    Class to control Eurotherm 2408 temperature controllers via EPICS.
    """

    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        pvName : `string`
            Power supply base naming of the PV (Process Variable)
        mnemonic : `string`
            Temperature controller mnemonic
        """
        super().__init__(mnemonic)

        self.device = Device(pvName + ':', ['PV:RBV', 'SP','RR', 'RR:RBV',
        'WSP:RBV', 'O' , 'O:RBV', 'MAN'])

        self.newTemp = Event()
        self.pvName = pvName



    def getValue(self):
        """
        Returns the current measured temperature.

        Returns
        -------
        `float`
        """
        return self.device.get('PV:RBV')


    def getSP(self):
        """
        Returns the current Set Point.

        Returns
        -------
        `float
        """
        time.sleep(0.5)
        return self.device.get('SP')


    def getTarget(self):
        """
        Returns the current target temperature.

        Returns
        -------
        `float`
        """
        time.sleep(0.5)
        return self.device.get('WSP:RBV')

    def getRealPosition(self):
        """
        Returns the same as :meth:`getValue`.

        See: :meth:`getValue`

        Returns
        -------
        `float`
        """
        return self.getValue()

    def getRampRate(self):
        """
        Returns the defined ramp rate.

        Returns
        -------
        `int`
        """
        return self.device.get('RR:RBV')

    def getLowLimitValue(self):
        """
        Returns the controller low limit temperature.

        Returns
        -------
        `float`
        """
        return 25.0

    def getHighLimitValue(self):
        """
        Returns the controller high limit temperature.

        Returns
        -------
        `float`
        """
        return 1000

    def getRRHighLimitValue(self):
        return 25.0

    def getRRLowLimitValue(self):
        return 1.0

    def setRampRate(self, value):
        self.device.put('RR', value)

    def stop(self):
        '''Define SP to minimum temperature on maximum ramp rate'''
        self.setRampRate(self.getRRHighLimitValue)
        self.setValue(self.getLowLimitValue())

    def hold(self):
        '''Set temperature to actual temperature'''
        actual_temp = self.getValue()
        self.setValue(actual_temp)

    def setValue(self, value, wait = False):
        if value < self.getLowLimitValue() or value > self.getHighLimitValue():
            raise ValueError('Value exceeds limits')
        self.device.put('SP', value)
        if wait:
            self.wait()

    def getP(self):
        """
        Return the current P value at the furnace

        Returns
        -------
        `double`
        """
        return self.device.get('P')

    def getI(self):
        """
        Return the current I value at the furnace

        Returns
        -------
        `double`
        """
        return self.device.get('I')

    def getD(self):
        """
        Return the current D value at the furnace

        Returns
        -------
        `double`
        """
        return self.device.get('D')

    def getPower(self):
        """
        Return the current Power value at the furnace

        Returns
        -------
        `double`
        """
        return self.device.get('O:RBV')

    def setPower(self, value):
        """
        Set Power value at the furnace

        Returns
        -------
        `double`
        """
        # it is necessary go to Manual mode to change power
        self.setManual()
        time.sleep(0.5)
        self.device.put('O', value)

    def setManual(self):
        """
        Set furnance to Manual mode
        """
        self.device.put('MAN', 1)

    def setAutomatic(self):
        """
        Set furnance to Automatic mode
        """
        self.device.put('MAN', 0)


    def reachTemp(self):
        if self.getValue() < self.getSP() + DELTA and \
          self.getValue() > self.getSP() - DELTA:
          return True
        return False


    def wait(self):
        """
        Blocks until the requested temperature is achieved.
        """
        self.newTemp.clear()
        while not self.reachTemp():
            ca.flush_io()
            self.newTemp.wait(5)
            self.newTemp.clear()

    def setVelocity(self, velo):
        """
        Same as :meth:`setRampRate`.

        See: :meth:`setRampRate`

        Parameters
        ----------
        r : `float`
            Ramp speed
        """
        self.setRampRate(velo)
Esempio n. 44
0
 def __init__(self, prefix, cam="cam1:"):
     Device.__init__(self, '%s%s' % (prefix, cam),
                     delim='', attrs=AD_CAM_ATTRS)
Esempio n. 45
0
    def __init__(self, prefix):
        self.device = Device(prefix, delim=':', attrs=self.attrs)

        time.sleep(0.1)
        self.read_pvfile()
Esempio n. 46
0
 def __init__(self, pvName, mnemonic):
     StandardDevice.__init__(self, mnemonic)
     self.pvName = pvName
     self.modem = Device(pvName+':FONE:', ('discar.PROC','audio','numero','discar.VALA'))
     self._status = self.getStatus()
     self.modem.add_callback('discar.VALA',self.onStatusChange)
Esempio n. 47
0
 def connect_pvs(self, prefix):
     self.adcam = Device(prefix,  delim='', attrs=self.ad_attrs)
     self.adcam.add_callback('cam1:ArrayCounter_RBV', self.onNewImage)
Esempio n. 48
0
class ADMonoImagePanel(wx.Panel):
    """Image Panel for monochromatic Area Detector"""

    ad_attrs = ('image1:ArrayData',
                'image1:ArraySize0_RBV',
                'image1:ArraySize1_RBV',
                'cam1:ArrayCounter_RBV')

    def __init__(self, parent, prefix=None, writer=None,
                 motion_writer=None, draw_objects=None, rot90=0,
                 thumbnail=None,
                 contrast_level=0, size=(600, 600), **kws):

        super(ADMonoImagePanel, self).__init__(parent, -1, size=size)

        self.drawing = False
        self.adcam = None
        self.image_id = -1
        self.x = self.y = 0
        self.writer = writer
        self.motion_writer = motion_writer
        self.thumbnail = thumbnail
        self.thumbmode = 'click'
        self.scale = 0.8
        self.colormap = None
        self.contrast_levels = [contrast_level, 100.0-contrast_level]
        self.rot90 = rot90
        self.flipv = False
        self.fliph = False
        self.image = None
        self.bitmap_size = (2, 2)
        self.data = np.arange(25).reshape(5, 5)
        self.panel_size = self.GetSize()
        self.draw_objects = None
        self.SetBackgroundColour("#E4E4E4")
        self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
        self.Bind(wx.EVT_SIZE, self.onSize)
        self.Bind(wx.EVT_PAINT, self.onPaint)
        if self.motion_writer is not None:
            self.Bind(wx.EVT_MOTION, self.onMotion)
        self.Bind(wx.EVT_LEFT_DOWN, self.onLeftDown)
        self.Bind(wx.EVT_RIGHT_DOWN, self.onRightDown)

        self.build_popupmenu()
        self.connect_pvs(prefix)
        self.restart_fps_counter()


    def restart_fps_counter(self, nsamples=100):
        self.capture_times = deque([], maxlen=nsamples)
        if self.writer is not None:
            self.writer("")

    def connect_pvs(self, prefix):
        self.adcam = Device(prefix,  delim='', attrs=self.ad_attrs)
        self.adcam.add_callback('cam1:ArrayCounter_RBV', self.onNewImage)

    def GetImageSize(self):
        return  (self.adcam.get('image1:ArraySize0_RBV'),
                 self.adcam.get('image1:ArraySize1_RBV'))

    def onMotion(self, evt=None):
        """report motion events within image"""
        if self.motion_writer is None and self.thumbnail is None:
            return
        if self.thumbmode == 'live':
            self.show_thumbnail(evt.GetX(), evt.GetY())

    def show_thumbnail(self, evt_x, evt_y):
        img_w, img_h = self.bitmap_size
        if self.rot90 in (1, 3):
            img_w, img_h = img_h, img_w
        pan_w, pan_h = self.panel_size
        pad_w, pad_h = (pan_w-img_w)/2.0, (pan_h-img_h)/2.0

        x = int(0.5 + (evt_x - pad_w)/self.scale)
        y = int(0.5 + (evt_y - pad_h)/self.scale)
        if self.rot90 in (1, 3):
            x, y = y, x
        self.x, self.y = x, y
        dh, dw = self.data.shape
        if y > -1 and y <  dh and x > -1 and x < dw:
            self.motion_writer(PIXEL_FMT %(x, y, self.data[y, x]))
            if self.thumbnail is not None:
                self.thumbnail.xcen = x
                self.thumbnail.ycen = y
                self.thumbnail.Refresh()
        else:
            self.motion_writer('')

    def build_popupmenu(self):
        self.popup_menu = popup = wx.Menu()
        MenuItem(self, popup, 'Follow Cursor for Thumbnail', '', self.thumb_motion_mode)
        MenuItem(self, popup, 'Left Click to Show Thumbnail', '',  self.thumb_click_mode)

    def thumb_motion_mode(self, evt=None):
        self.thumbmode = 'live'

    def thumb_click_mode(self, evt=None):
        self.thumbmode = 'click'

    def onLeftDown(self, evt=None):
        if self.thumbmode == 'click':
            self.show_thumbnail(evt.GetX(), evt.GetY())

    def onRightDown(self, evt=None):
        wx.CallAfter(self.PopupMenu, self.popup_menu, evt.GetPosition())

    @DelayedEpicsCallback
    def onNewImage(self, pvname=None, value=None, **kws):
        if value > self.image_id and not self.drawing:
            self.drawing = True
            self.image_id = value
            self.Refresh()
            self.drawing = False


    def GrabNumpyImage(self):
        """get raw image data, as numpy ndarray, correctly shaped"""
        if True:
            data = self.adcam.PV('image1:ArrayData').get()
        else: # except:
            data = None
        if data is not None:
            w, h = self.GetImageSize()
            data = data.reshape((h, w))
            if self.flipv:
                data = data[::-1, :]
            if self.fliph:
                data = data[:, ::-1]
            if self.rot90 == 1:
                data = data.transpose()
            elif self.rot90 == 2:
                data = data[::-1, ::-1]
            elif self.rot90 == 3:
                data = data[::-1, ::-1].transpose()

        poll()
        return data

    def GrabWxImage(self):
        """get wx Image:
        - scaled in size
        - color table applied
        - flipped and/or rotated
        - contrast levels set
        """
        data = self.GrabNumpyImage()
        if data is None:
            return
        self.capture_times.append(time.time())
        self.data = data
        jmin, jmax = np.percentile(data, self.contrast_levels)
        if self.thumbnail is not None:
            self.thumbnail.contrast_levels = self.contrast_levels
            self.thumbnail.colormap = self.colormap

        data = (np.clip(data, jmin, jmax) - jmin)/(jmax+0.001)
        h, w = data.shape # self.GetImageSize()

        if callable(self.colormap):
            data = self.colormap(data)
            if data.shape[2] == 4: # with alpha channel
                image = wx.Image(w, h,
                                 (data[:,:,:3]*255.).astype('uint8'),
                                 (data[:,:,3]*255.).astype('uint8'))
            else:
                image = wx.Image(w, h, (data*255.).astype('uint8'))
        else:
            rgb = np.zeros((h, w, 3), dtype='float')
            rgb[:, :, 0] = rgb[:, :, 1] = rgb[:, :, 2] = data
            image = wx.Image(w, h, (rgb*255.).astype('uint8'))

        return image.Scale(int(self.scale*w), int(self.scale*h))

    def onSize(self, evt=None):
        if evt is not None:
            fh, fw = evt.GetSize()
        else:
            fh, fw = self.GetSize()

        h, w = self.GetImageSize()
        if self.rot90 in (1, 3):
            w, h = h, w

        self.scale = max(0.10, min(0.98*fw/(w+0.1), 0.98*fh/(h+0.1)))
        wx.CallAfter(self.Refresh)

    def onPaint(self, event):
        image = self.GrabWxImage()
        if image is None:
            return
        if len(self.capture_times) > 2 and self.writer is not None:
            ct = self.capture_times
            fps = (len(ct)-1) / (ct[-1]-ct[0])
            self.writer("Image %d: %.1f fps" % (self.image_id, fps))
        bitmap = wx.Bitmap(image)
        self.full_size = image.GetSize()
        bmp_w, bmp_h = self.bitmap_size = bitmap.GetSize()
        pan_w, pan_h = self.panel_size = self.GetSize()
        pad_w, pad_h = int(1+(pan_w-bmp_w)/2.0), int(1+(pan_h-bmp_h)/2.0)
        dc = wx.AutoBufferedPaintDC(self)
        dc.Clear()
        dc.DrawBitmap(bitmap, pad_w, pad_h, useMask=True)
        x, y = self.x, self.y
        dh, dw = self.data.shape
        if y > -1 and y <  dh and x > -1 and x < dw:
            self.motion_writer(PIXEL_FMT %(x, y, self.data[y, x]))
        if self.thumbnail is not None:
            self.thumbnail.data = self.data
            self.thumbnail.Refresh()


    def __draw_objects(self, dc, img_w, img_h, pad_w, pad_h):
        dc.SetBrush(wx.Brush('Black', wx.BRUSHSTYLE_TRANSPARENT))
        if self.draw_objects is not None:
            for obj in self.draw_objects:
                shape = obj.get('shape', None)
                color = obj.get('color', None)
                if color is None:
                    color = obj.get('colour', 'Black')
                color = wx.Colour(*color)
                width = obj.get('width', 1.0)
                style = obj.get('style', wx.SOLID)
                args  = obj.get('args', [0, 0, 0, 0])
                kws   = obj.get('kws', {})

                method = getattr(dc, 'Draw%s' % (shape.title()), None)
                if shape.title() == 'Line':
                    margs = [pad_w + args[0]*img_w,
                             pad_h + args[1]*img_h,
                             pad_w + args[2]*img_w,
                             pad_h + args[3]*img_h]
                elif shape.title() == 'Circle':
                    margs = [pad_w + args[0]*img_w,
                             pad_h + args[1]*img_h,  args[2]*img_w]

                if method is not None:
                    dc.SetPen(wx.Pen(color, width, style))
                    method(*margs, **kws)
Esempio n. 49
0
class ConfPanel_EpicsAD(ConfPanel_Base):
    img_attrs = ('ArrayData', 'UniqueId_RBV', 'NDimensions_RBV',
                 'ArraySize0_RBV', 'ArraySize1_RBV', 'ArraySize2_RBV',
                 'ColorMode_RBV')

    cam_attrs = ('Acquire', 'ArrayCounter', 'ArrayCounter_RBV',
                 'DetectorState_RBV', 'NumImages', 'ColorMode', 'DataType_RBV',
                 'Gain', 'AcquireTime', 'AcquirePeriod', 'ImageMode',
                 'MaxSizeX_RBV', 'MaxSizeY_RBV', 'TriggerMode', 'SizeX',
                 'SizeY', 'MinX', 'MinY')

    def __init__(self,
                 parent,
                 image_panel=None,
                 prefix=None,
                 center_cb=None,
                 xhair_cb=None,
                 **kws):
        super(ConfPanel_EpicsAD, self).__init__(parent,
                                                center_cb=center_cb,
                                                xhair_cb=xhair_cb)

        wids = self.wids
        sizer = self.sizer
        self.image_panel = image_panel
        self.SetBackgroundColour('#EEFFE')
        self.title = wx.StaticText(self,
                                   size=(285, 25),
                                   label="Epics AreaDetector")

        for key in ('imagemode', 'triggermode', 'color'):
            self.wids[key] = PVEnumChoice(self, pv=None, size=(135, -1))

        for key in ('exptime', 'gain'):
            self.wids[key] = PVFloatCtrl(self,
                                         pv=None,
                                         size=(135, -1),
                                         minval=0)
        self.wids['gain'].SetMax(20)

        for key in ('start', 'stop'):
            self.wids[key] = wx.Button(self,
                                       -1,
                                       label=key.title(),
                                       size=(65, -1))
            self.wids[key].Bind(wx.EVT_BUTTON, Closure(self.onButton, key=key))

        labstyle = wx.ALIGN_LEFT | wx.EXPAND | wx.ALIGN_BOTTOM
        ctrlstyle = wx.ALIGN_LEFT  #  |wx.ALIGN_BOTTOM
        rlabstyle = wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP | wx.EXPAND
        txtstyle = wx.ALIGN_LEFT | wx.ST_NO_AUTORESIZE | wx.TE_PROCESS_ENTER

        self.wids['fullsize'] = wx.StaticText(self,
                                              -1,
                                              size=(250, -1),
                                              style=txtstyle)

        def txt(label, size=100):
            return wx.StaticText(self,
                                 label=label,
                                 size=(size, -1),
                                 style=labstyle)

        def lin(len=30, wid=2, style=wx.LI_HORIZONTAL):
            return wx.StaticLine(self, size=(len, wid), style=style)

        sizer.Add(self.title, (0, 0), (1, 3), labstyle)
        i = next_row = self.show_position_info(row=1)
        i += 1
        sizer.Add(self.wids['fullsize'], (i, 0), (1, 3), labstyle)
        i += 1
        sizer.Add(txt('Acquire '), (i, 0), (1, 1), labstyle)
        sizer.Add(self.wids['start'], (i, 1), (1, 1), ctrlstyle)
        sizer.Add(self.wids['stop'], (i, 2), (1, 1), ctrlstyle)
        i += 1
        sizer.Add(txt('Image Mode '), (i, 0), (1, 1), labstyle)
        sizer.Add(self.wids['imagemode'], (i, 1), (1, 2), ctrlstyle)

        i += 1
        sizer.Add(txt('Trigger Mode '), (i, 0), (1, 1), labstyle)
        sizer.Add(self.wids['triggermode'], (i, 1), (1, 2), ctrlstyle)

        i += 1
        sizer.Add(txt('Exposure Time '), (i, 0), (1, 1), labstyle)
        sizer.Add(self.wids['exptime'], (i, 1), (1, 2), ctrlstyle)

        i += 1
        sizer.Add(txt('Gain '), (i, 0), (1, 1), labstyle)
        sizer.Add(self.wids['gain'], (i, 1), (1, 2), ctrlstyle)

        i += 1
        sizer.Add(txt('Color Mode'), (i, 0), (1, 1), labstyle)
        sizer.Add(self.wids['color'], (i, 1), (1, 2), ctrlstyle)

        #  show last pixel position, move to center
        pack(self, sizer)
        self.set_prefix(prefix)

    @EpicsFunction
    def set_prefix(self, prefix):
        if prefix.endswith(':'): prefix = prefix[:-1]
        if prefix.endswith(':image1'): prefix = prefix[:-7]
        if prefix.endswith(':cam1'): prefix = prefix[:-5]
        self.prefix = prefix

        self.ad_img = Device(prefix + ':image1:',
                             delim='',
                             attrs=self.img_attrs)
        self.ad_cam = Device(prefix + ':cam1:', delim='', attrs=self.cam_attrs)

        self.title.SetLabel("Epics AreaDetector: %s" % prefix)
        self.connect_pvs()

    @EpicsFunction
    def connect_pvs(self, verbose=True):
        if self.prefix is None or len(self.prefix) < 2:
            return

        time.sleep(0.010)
        if not self.ad_img.PV('UniqueId_RBV').connected:
            poll()
            if not self.ad_img.PV('UniqueId_RBV').connected:
                self.messag('Warning:  Camera seems to not be connected!')
                return

        self.wids['color'].SetPV(self.ad_cam.PV('ColorMode'))
        self.wids['exptime'].SetPV(self.ad_cam.PV('AcquireTime'))
        self.wids['gain'].SetPV(self.ad_cam.PV('Gain'))
        self.wids['imagemode'].SetPV(self.ad_cam.PV('ImageMode'))

        self.wids['triggermode'].SetPV(self.ad_cam.PV('TriggerMode'))

        width, height = self.image_panel.GetImageSize()
        sizelabel = 'Image Size: %i x %i pixels' % (width, height)

        self.wids['fullsize'].SetLabel(sizelabel)
        poll()

    def onBringToCenter(self, event=None, **kws):
        if self.center_cb is not None:
            self.center_cb(event=event, **kws)

    @EpicsFunction
    def onButton(self, evt=None, key='name', **kw):
        if evt is None:
            return
        if key == 'start':
            self.n_img = 0
            self.n_drawn = 0
            self.starttime = time.time()
            self.imgcount_start = self.ad_cam.ArrayCounter_RBV
            self.ad_cam.Acquire = 1
        elif key == 'stop':
            self.ad_cam.Acquire = 0
        elif key == 'unzoom':
            self.unZoom()
        else:
            print('unknown Entry ? ', key)
Esempio n. 50
0
class Collector:
    attrs = ('status', 'mode', 'request',
             'host', 'folder', 'filename', 'fileext', 'format',
             'message', 'timestamp', 'unixtime',
             'arg1', 'arg2', 'arg3', 'counttime')
    
    def __init__(self, prefix):
        self.device = Device(prefix, delim=':', attrs=self.attrs)

        time.sleep(0.1)
        self.read_pvfile()
        
        # self.device.add_callback('request', self.onRequest)
    def onRequest(self,  pvname=None, value=None, **kws):
        print 'Request changed to ', value

    def read_pvfile(self):
        try:
            f = open(PVFILE1, 'r')
            lines = f.readlines()
            f.close()
        except:
            self.env_pvs = None
            return
        self.env_pvs = []
        for i in lines:
            i = i[:-1].strip()
            if len(i)<2: continue
            words = i.split()
            pvname = words.pop(0)
            desc = ' '.join(words).strip()
            pv = PV(pvname)
            pv.get()
            if pv:
                if desc == '': desc = pv.desc
                self.env_pvs.append( (pv,desc) )
        print 'Will use %i  PVs from %s' % (len(self.env_pvs), PVFILE1)

        
    def setTime(self, ts=None):
        if ts is None:
            ts = time.time()
        self.device.timestamp = time.ctime(ts)  # self.device.timestamp = Py:EXT:timestamp
        self.device.unixtime  = ts

    def setMessage(self, msg):
        self.device.message = msg

    def setStatus(self, status):
        self.device.status  =status

    def write_file(self):
        host     = self.device.get('host', as_string=True)
        folder   = self.device.get('folder', as_string=True)
        filename = self.device.get('filename', as_string=True)
        fileext  = self.device.get('fileext', as_string=True)
        format   = self.device.get('format', as_string=True)
        if format == '':
            format = '%s.%s'
            
        filename = format % (filename, fileext)
        filename = os.path.join(host, folder, filename)

        print 'Write to File: ', filename
        print 'Mode =',  self.device.mode

        # Write to Master Mapping file:
        # maybe add PVs to epics py_example.db for
        # sampleX, sampleY, sampleName
        mapfile = open(MAPFILENAME, 'a')
        mapfile.write("%s , %s , %s  : %s\n" % (xpos, ypos, samplename, filename))
        mapfile.close()

        #
        # put real commands here
        if self.device.mode == 0:
            self.setMessage(' writing (Mode 0)....')
            time.sleep(2.0)            
            os.system(burt_command0)
            # newval = epics.caget(SOME_OTHER_PV)

            # write data from PVLIST
            try:
                fout = open(filename, 'w')
            except:
                print 'could not open file %s for writing ' % filename
            for pv, desc in self.env_pvs:
                try:
                    fout.write('%s || %s || %s \n' % (pv.pvname,pv.char_value,desc))
                except:
                    pass
            fout.close()
            
            self.setMessage(' wrote %s ' % filename)
            #
            
        elif self.device.mode == 1:
            self.setMessage(' writing (Mode 1)....')            
            time.sleep(3.0)
        self.setMessage(' cleaning up ....')
        time.sleep(1.0)

    def run(self):
        self.setMessage('Starting...')
        self.device.request = 0   # == 'caput Py:EXT:request 0'
        self.setTime()
        time.sleep(0.1)
        while True:
            time.sleep(0.1)
            self.setTime()
            if self.device.request == 1: # start # 'caget Py:EXT:request =? 1'
                self.setMessage(' Starting ....')
                self.setStatus(1)
                if self.device.mode == 0: #  Py:EXT:mode
                    self.write_file()

                elif self.device.mode == 1: #  Py:EXT:mode
                    # self.read_mode0_file()
                    # self.write_something_else()
                    self.write_file()                    
              
                self.setMessage(' Done.')                    
                self.setStatus(0)
                self.device.request = 0
            elif self.device.request == 0: # stop
                pass
            elif self.device.request == 2: # pause
                print 'pause not implemented'
            elif self.device.request == 3: # resume
                print 'resume not implemented'
            elif self.device.request == 4: # shutdown
                break

        self.setMessage(' Shutting down ....')
        self.setStatus(3)
        self.setTime(0)
Esempio n. 51
0
class OmronE5CK(StandardDevice, IScannable):
    """
    Class to control Omron E5CK temperature controllers via EPICS.

    Examples
    --------
    >>> from py4syn.epics.OmronE5CKClass import OmronE5CK
    >>> 
    >>> def showTemperature(pv='', name=''):
    ...     e5ck = OmronE5CK(pv, name)
    ...     print('Temperature is: %d' % e5ck.getValue())
    ...
    >>> def fastRaiseTemperature(e5ck, amount, rate=30):
    ...     e5ck.setRate(rate)
    ...     e5ck.setValue(e5ck.getValue() + amount)
    ...
    >>> def complexRamp(e5ck):
    ...     e5ck.setRate(10)
    ...     e5ck.setValue(200)
    ...     e5ck.wait()
    ...     e5ck.setRate(2)
    ...     e5ck.setValue(220)
    ...     e5ck.wait()
    ...     sleep(500)
    ...     e5ck.setRate(5)
    ...     e5ck.setValue(100)
    ...     e5ck.wait()
    ...     e5ck.stop()
    ...
    >>> import py4syn
    >>> from py4syn.epics.ScalerClass import Scaler
    >>> from py4syn.utils.counter import createCounter
    >>> from py4syn.utils.scan import scan
    >>> 
    >>> def temperatureScan(start, end, rate, pv='', counter='', channel=2):
    ...     e5ck = OmronE5CK(pv, 'e5ck')
    ...     py4syn.mtrDB['e5ck'] = e5ck
    ...     c = Scaler(counter, channel, 'simcountable')
    ...     createCounter('counter', c, channel)
    ...     e5ck.setRate(rate)
    ...     scan('e5ck', start, end, 10, 1)
    ...     e5ck.stop()
    ...
    """

    STATUS_IS_RUNNING = 1<<7
    PROGRAM_LENGTH = 4
    COMMAND_GET_STEP = '4010000'
    COMMAND_SET_TARGET = '5%02d%04d'
    TARGETS = (5, 8, 11, 14,)
    TIMES = (7, 10, 13, 16,)

    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        pvName : `string`
            Power supply base naming of the PV (Process Variable)
        mnemonic : `string`
            Temperature controller mnemonic
        """
        super().__init__(mnemonic)

        self.device = Device(pvName + ':', ['termopar', 'target', 'status', 'stepNum',
                             'programTable', 'programming', 'run', 'stop', 'advance',
                             'setPatternCount', 'timeScale', 'level1', 'reset', 'pause',
                             'sendCommand'])

        self.programmingDone = Event()
        self.newTemperature = Event()
        self.newStep = Event()
        self.device.add_callback('programming', self.onProgrammingChange)
        self.device.add_callback('termopar', self.onTemperatureChange)
        self.device.add_callback('stepNum', self.onStepChange)
        self.timeScaleCache = self.device.get('timeScale')

        self.pvName = pvName
        self.rate = 5
        self.presetDone = False

    def __str__(self):
        return '%s (%s)' % (self.getMnemonic(), self.pvName)

    def isRunning(self):
        """
        Returns true if the controller is in program mode. Whenever it is program mode,
        it is following a target temperature.

        Returns
        -------
        `bool`
        """
        v = self.device.get('status')
        r = not bool(int(v) & self.STATUS_IS_RUNNING)
        if not r:
            self.presetDone = False

        return r

    def getValue(self):
        """
        Returns the current measured temperature.

        Returns
        -------
        `float`
        """
        return self.device.get('termopar')

    def getTarget(self):
        """
        Returns the current target temperature. If the device is running, the target
        temperature is the temperature the device is changing to. If the device is not
        running, the target temperature is ignored.

        Returns
        -------
        `float`
        """
        return self.device.get('target')

    def getRealPosition(self):
        """
        Returns the same as :meth:`getValue`.

        See: :meth:`getValue`

        Returns
        -------
        `float`
        """
        return self.getValue()

    def getStepNumber(self):
        """
        Helper method to get the current program step.

        Returns
        -------
        `int`
        """
        return self.device.get('stepNum')

    def getLowLimitValue(self):
        """
        Returns the controller low limit temperature.

        Returns
        -------
        `float`
        """
        return 0.0

    def getHighLimitValue(self):
        """
        Returns the controller high limit temperature.

        Returns
        -------
        `float`
        """
        return 1300.0

    def onProgrammingChange(self, value, **kwargs):
        """
        Helper callback that tracks when the IOC finished programming the device.
        """
        self.presetDone = False
        if value == 0:
            self.programmingDone.set()

    def onStepChange(self, value, **kwargs):
        """
        Helper callback that indicates when a new program step has been reached
        """
        self.newStep.set()

    def onTemperatureChange(self, value, **kwargs):
        """
        Helper callback that indicates when the measured temperature has changed
        """
        self.newTemperature.set()

    def stop(self):
        """
        Stops executing the current temperature program and puts the device in idle
        state. In the idle state, the device will not try to set a target temperature.
        """
        self.device.put('stop', 1)
        self.presetDone = False

    def run(self):
        """
        Starts or resumes executing the current temperature program.
        """
        self.device.put('run', 1)

    def advance(self):
        """
        Helper method to skip the current program step and execute the next one.
        """
        self.device.put('advance', 1)

    def pause(self):
        """
        Pauses current ramp program. To resume program, use :meth:`run`

        See: :meth:`run`
        """
        self.device.put('pause', 1)

    def sendCommand(self, command):
        """
        Helper method to send a custom command to the controller.

        Parameters
        ----------
        command : `str`
            The command to be send
        """
        self.device.put('sendCommand', command.encode(), wait=True)

    def preset(self):
        """
        Makes the controler enter a well defined known state. This method creates and
        runs an "empty" ramp program. The program simply mantains the current
        temperature forever, whatever that temperature is. This is mostly a helper
        function, to allow making complex temperature ramps starting from a known
        state and reusing the preset values.

        .. note::
            Running a new program requires stopping the current program. While the
            program is stopped, the controller power generation drops to zero. Because
            of this power drop, this method may be slow to stabilize.
        """
        self.stop()
        current = self.getValue()

        # Steps 0 and 2 are fake steps, steps 1 and 3 are the real ones.
        # The fake steps are used for synchronizing with the device.
        program = [self.PROGRAM_LENGTH] + self.PROGRAM_LENGTH*[current, 99]
        self.programmingDone.clear()
        self.device.put('setPatternCount', 9999)
        self.device.put('programTable', array(program))
        ca.flush_io()
        self.programmingDone.wait(10)
        self.run()
        self.presetDone = True

    def getTimeScale(self):
        """
        Returns the time scale being used by the controller. The timescale can either
        be zero, for hours:minutes, or one, for minutes:seconds.

        Returns
        -------
        `int`
        """
        t = self.device.PV('timeScale')
        v = t.get()
        t.get_ctrlvars()
        if t.severity == 0:
            self.timeScaleCache = v

        return self.timeScaleCache

    def setTimeScale(self, minutes):
        """
        Changes the time scale being used by the controller. The timescale can either
        be zero, for hours:minutes, or one, for minutes:seconds. This operation requires
        switching the controller operation mode to be successful, and then a reset is
        issued after it. The whole operation takes more than 5 seconds.

        Parameters
        ----------
        minutes : `int`
            Set to 1 for minutes:seconds, or 0 for hours:minutes
        """
        if minutes == self.getTimeScale() and self.device.PV('timeScale').severity == 0:
            return

        t = self.getValue()

        self.device.put('level1', 1)
        self.device.put('timeScale', minutes)
        self.device.put('reset', 1)

    def getStepNumberSync(self):
        """
        Helper module to retrieve an up-to-date value for the current program step
        number. Similar to :meth:`getStepNumber`, but it doesn't rely on monitor value
        and instead does a synchronous caget() call.

        See: :meth:`getStepNumber`

        Returns
        -------
        `int`
        """
        self.device.put('stepNum.PROC', 0, wait=True)
        v = self.device.PV('stepNum').get(use_monitor=False)
        return int(v)

    def synchronizeStep(self, current):
        """
        Helper method to set up a constant temperature right before running a ramp
        program. This method detects if a current ramp program is running or not. If
        it's not, then it doesn't do anything. If there is a ramp running, then it
        configures and advances to a "synchronization step", that is, a step where
        the temperature does not change. This step marks the beginning of the new
        ramp.

        The method returns the resulting step number

        Parameters
        ----------
        current : `float`
            The temperature target for the synchronization step

        Returns
        -------
        `int`
        """

        # This method uses the advance() call to skip steps. Suprisingly, advancing
        # steps with E5CK is not trivial. The reason for this is that E5CK quickly
        # acknowledges the advance command, but delays to actually advance. Ignoring
        # this deficiency results in other commands issued later doing the wrong thing.
        # In particular, calling advance again later may silently fail. We work around
        # this by using a synchronous call to get the current step number and a busy
        # wait to check when the step was really changed.
        #
        # To make things worse, some component in EPICS seems to break serialization by
        # not respecting the order which PVs are updated, so it's not possible to
        # change the program using two separate PVs, like, for example, stepNumConfig
        # setStepTarget, which are implemented in E5CK's IOC. Because of that, a custom
        # PV was added in the IOC to support arbitrary commands sent in a serialized
        # way. This sendCommand procedure is what this method uses.
        step = self.getStepNumberSync()
        while step % 2 == 1:
            target = self.TARGETS[(step+1)%self.PROGRAM_LENGTH]
            self.sendCommand(self.COMMAND_SET_TARGET % (target, current))
            self.advance()

            # E5CK is slow, so loop until it changes state. This is required: calling
            # advance twice in a row doesn't work. A state transition must happen first.
            old = step
            while old == step:
                step = self.getStepNumberSync()
        assert step % 2 == 0

        return step

    def timeToValue(self, t):
        """
        Helper method to convert between minutes to the format used by the controller.

        Parameters
        ----------
        t : `float`
            The desired time, in minutes

        Returns
        -------
        `float`
        """
        if self.getTimeScale() == 0:
            minutes = int(t)%60
            hours = int(t)//60
            value = 100*hours + minutes

            if hours > 99:
                raise OverflowError('Ramp time is too large: %g' % rampTime)
        else:
            minutes = int(t)

            if minutes > 99:
                raise OverflowError('Ramp time is too large with current settings: %g' %
                                    t)

            seconds = min(round((t-minutes)*60), 59)
            value = 100*minutes + seconds

        return value

    def setRate(self, r):
        """
        Sets the ramp speed in degrees per minutes for use with :meth:`setValue`. This
        method does not send a command to the controller, it only stores the rate for
        the next ramps.

        See: :meth:`setValue`

        Parameters
        ----------
        r : `float`
            Ramp speed in °C/min
        """
        self.rate = r

    def setValue(self, v):
        """
        Changes the temperature to a new value. This method calls preset if it has not
        already been called first. The speed that the new temperature is reached is set
        with :meth:`setRate`. The default rate is 5 °C/minute.

        See: :meth:`setRate`

        Parameters
        ----------
        v : `float`
            The target temperature in °C
        """

        # This method depends on a program preset being loaded and the program being
        # in a synchronization step. Given the two precondition, this method simply
        # programs a ramp, a synchronization step after the ramp and advances to the
        # ramp step.
        if not self.presetDone:
            self.preset()

        # We accept float as input, but the controller is integer only
        v = round(v)

        current = self.getValue()
        minutes = abs(v-current)/self.rate
        time = self.timeToValue(minutes)

        step = self.synchronizeStep(current)
        self.waitStep = (step+2)%self.PROGRAM_LENGTH
        x = self.TARGETS[step+1]
        y = self.TIMES[step+1]
        z = self.TARGETS[self.waitStep]
        self.sendCommand(self.COMMAND_SET_TARGET % (x, v))
        self.sendCommand(self.COMMAND_SET_TARGET % (y, time))
        self.sendCommand(self.COMMAND_SET_TARGET % (z, v))
        self.advance()
        self.valueTarget = v

    def wait(self):
        """
        Blocks until the requested temperature is achieved.
        """
        if not self.presetDone:
            return

        # Waiting is done in two steps. First step waits until the program reaches
        # the next synchronization step. Second step waits util the measured temperature
        # reaches the requested temperature
        self.newStep.clear()
        while self.getStepNumber() != self.waitStep:
            ca.flush_io()
            self.newStep.wait(60)
            self.newStep.clear()

        self.newTemperature.clear()
        while self.getValue() != self.valueTarget:
            ca.flush_io()

            # Safety timeout, temperature didn't change after a long time
            if not self.newTemperature.wait(120):
                return

            self.newTemperature.clear()
Esempio n. 52
0
class Motor(IScannable, StandardDevice):
    """
    Class to control motor devices via EPICS.

    Examples
    --------
    >>> from py4syn.epics.MotorClass import Motor
    >>>    
    >>> def createMotor(pvName="", mne=""):
    ...    
    ...    new_motor = ''
    ...    
    ...    try:
    ...        new_motor = Motor(pvName, mne)
    ...            print "Motor " + pvName + " created with success!"
    ...    except Exception,e:
    ...        print "Error: ",e
    ...    
    ...    return new_motor
    """

    def onStatusChange(self, value, **kw):
        self._moving = not value

    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`
        
        Parameters
        ----------
        pvName : `string`
            Motor's base naming of the PV (Process Variable)
        mnemonic : `string`
            Motor's mnemonic
        """
        StandardDevice.__init__(self, mnemonic)
        self.pvName = pvName

        self.pvType = PV(pvName+".RTYP", connection_timeout=3)

        if(self.pvType.status == None):
            raise Exception("Epics PV "+ pvName+" seems to be offline or not reachable")

        if(self.pvType.get()!= "motor"):
            raise Exception(pvName+" is not an Epics Motor")
            

        self.motor = Device(pvName+'.', ('RBV','VAL', 'DRBV', 'DVAL','RLV', 'RVAL', 'RRBV',
                                         'STOP','MOVN','LLS','HLS','SSET','SUSE',
                                         'SET','VELO','EGU','DMOV','STUP', 'DESC',
                                         'BDST', 'HLM', 'LLM', 'DHLM', 'DLLM',
                                         'VOF','FOF','OFF', 'DIR','LVIO'))

        self.motor.add_callback('DMOV',self.onStatusChange)
        self._moving = self.isMovingPV()
        
        self.motorDesc = self.getDescription()

    def __str__(self):
        return self.getMnemonic() + "("+self.pvName+")"

    def getDirection(self):
        """
        Read the motor direction based on the `DIR` (User Direction) field of
        Motor Record

        Returns
        -------
        `integer`

        .. note::
            0. Positive direction;
            1. Negative direction.
        """
        return self.motor.get('DIR')

    def isMovingPV(self):
        """
        Check if a motor is moving or not from the PV

        Returns
        -------
        `boolean`

        .. note::
            - **True**  -- Motor is moving;
            - **False** -- Motor is stopped.
        """

        return (self.motor.get('DMOV') == 0)

    def isMoving(self):
        """
        Check if a motor is moving or not based on the callback

        Returns
        -------
        `boolean`

        .. note::
            - **True** -- Motor is moving;
            - **False** -- Motor is stopped.
        """

        return self._moving

    def isAtLowLimitSwitch(self):
        """
        Check if a motor low limit switch is activated, based on the `LLS` (At
        Low Limit Switch) field of Motor Record

        Returns
        -------
        `boolean`

        .. note::
            - **True** -- Motor is at Low Limit;
            - **False** -- Motor is **NOT** at Low Limit.
        """

        return self.motor.get('LLS')

    def isAtHighLimitSwitch(self):
        """
        Check if a motor high limit switch is activated, based on the `HLS`
        (At High Limit Switch) field of Motor Record

        Returns
        -------
        `boolean`

        .. note::
            - **True** -- Motor is at High Limit;
            - **False** -- Motor is **NOT** at High Limit.
        """

        return self.motor.get('HLS')

    def getDescription(self):
        """
        Read the motor descrition based on the `DESC` field of Motor Record

        Returns
        -------
        `string`
        """

        return self.motor.get('DESC')

    def getHighLimitValue(self):
        """
        Read the motor high limit based on the `HLM` (User High Limit) field of
        Motor Record

        Returns
        -------
        `double`
        """

        return self.motor.get('HLM')

    def getLowLimitValue(self):
        """
        Read the motor low limit based on the `LLM` (User Low Limit) field of
        Motor Record

        Returns
        -------
        `double`
        """

        return self.motor.get('LLM')

    def getDialHighLimitValue(self):
        """
        Read the motor dial high limit based on the `DHLM` (Dial High Limit)
        field of Motor Record

        Returns
        -------
        `double`
        """

        return self.motor.get('DHLM')

    def getDialLowLimitValue(self):
        """
        Read the motor dial low limit based on the `DLLM` (Dial Low Limit)
        field of Motor Record

        Returns
        -------
        `double`
        """

        return self.motor.get('DLLM')

    def getBacklashDistanceValue(self):
        """
        Read the motor backlash distance based on the `BDST` (Backlash Distance,
        `EGU`) field of Motor Record

        Returns
        -------
        `double`
        """

        return self.motor.get('BDST')

    def getVariableOffset(self):
        """
        Read the motor variable offset based on the `VOF` (Variable Offset)
        field of Motor Record

        Returns
        -------
        `integer`
        """

        return self.motor.get('VOF')

    def getFreezeOffset(self):
        """
        Read the motor freeze offset based on the `FOF` (Freeze Offset) field
        of Motor Record

        Returns
        -------
        `integer`
        """

        return self.motor.get('FOF')

    def getOffset(self):
        """
        Read the motor offset based on the `OFF` (User Offset, `EGU`) field of
        Motor Record

        Returns
        -------
        `string`
        """

        return self.motor.get('OFF')

    def getRealPosition(self):
        """
        Read the motor real position based on the `RBV` (User Readback Value)
        field of Motor Record

        Returns
        -------
        `double`
        """

        return self.motor.get('RBV')


    def getDialRealPosition(self):
        """
        Read the motor DIAL real position based on the `DRBV` (Dial Readback
        Value) field of Motor Record

        Returns
        -------
        `double`
        """

        return self.motor.get('DRBV')

    def getDialPosition(self):
        """
        Read the motor target DIAL position based on the `DVAL` (Dial Desired
        Value) field of Motor Record

        Returns
        -------
        `double`
        """

        return self.motor.get('DVAL')

    def getRawPosition(self):
        """
        Read the motor RAW position based on the `RVAL` (Raw Desired Value)
        field of Motor Record

        Returns
        -------
        `double`
        """

        return self.motor.get('RVAL')

    def setRawPosition(self, position):
        """
        Sets the motor RAW position based on the `RVAL` (Raw Desired Value)
        field of Motor Record

        Returns
        -------
        `double`
        """
        self._moving = True
        self.motor.put('RVAL', position)

        ca.poll(evt=0.05)

    def getRawRealPosition(self):
        """
        Read the motor RAW real position based on the `RRBV` (Raw Readback Value)
        field of Motor Record

        Returns
        -------
        `double`
        """

        return self.motor.get('RRBV')

    def getPosition(self):
        """
        Read the motor target position based on the `VAL` (User Desired Value)
        field of Motor Record

        Returns
        -------
        `double`
        """

        return self.motor.get('VAL')

    def getEGU(self):
        """
        Read the motor engineering unit based on the `EGU` (Engineering Units)
        field of Motor Record

        Returns
        -------
        `string`
        """

        return self.motor.get('EGU')

    def getLVIO(self):
        """
        Read the motor limit violation `LVIO` (Limit Violation) field of
        Motor Record

        Returns
        -------
        `short`
        """

        return self.motor.get('LVIO')

    def setEGU(self, unit):
        """
        Set the motor engineering unit to the `EGU` (Engineering Units) field
        of Motor Record

        Parameters
        ----------
        unit : `string`
            The desired engineering unit.

            .. note::
                **Example:** "mm.", "deg."
        """

        return self.motor.set('EGU', unit)

    def setHighLimitValue(self, val):
        """
        Set the motor high limit based on the `HLM` (User High Limit) field of
        Motor Record

        Parameters
        ----------
        val : `double`
            The desired value to set
        """

        self.motor.put('HLM', val)

    def setLowLimitValue(self, val):
        """
        Set the motor low limit based on the `LLM` (User Low Limit) field of
        Motor Record

        Parameters
        ----------
        val : `double`
            The desired value to set
        """

        self.motor.put('LLM', val)

    def setDialHighLimitValue(self, val):
        """
        Set the motor dial high limit based on the `DHLM` (Dial High Limit)
        field of Motor Record

        Parameters
        ----------
        val : `double`
            The desired value to set
        """

        self.motor.put('DHLM', val)

    def setDialLowLimitValue(self, val):
        """
        Set the motor dial low limit based on the `DLLM` (Dial Low Limit)
        field of Motor Record

        Parameters
        ----------
        val : `double`
            The desired value to set
        """

        self.motor.put('DLLM', val)

    def setSETMode(self):
        """
        Put the motor in SET mode

        .. note::
            Motor will **NOT** move until it is in in **USE mode**
        """

        self.motor.put('SSET',1)

    def setUSEMode(self):
        """
        Put the motor in **USE mode**
        """

        self.motor.put('SUSE',1)


    def setVariableOffset(self, val):
        """
        Set the motor variable offset based on the `VOF` (Variable Offset)
        field of Motor Record

        Parameters
        ----------
        val : `integer`
            The desired value to set
        """

        self.motor.put('VOF', val)

    def setFreezeOffset(self, val):
        """
        Set the motor freeze offset based on the `FOF` (Freeze Offset) field
        of Motor Record

        Parameters
        ----------
        val : `integer`
            The desired value to set
        """

        self.motor.put('FOF', val)

    def setOffset(self, val):
        """
        Set the motor offset based on the `OFF` (User Offset, `EGU`) field of
        Motor Record

        Parameters
        ----------
        val : `double`
            The desired value to set
        """

        self.motor.put('OFF', val)

    def setDialPosition(self, pos, waitComplete=False):
        """
        Set the motor target DIAL position based on the `DVAL` (Dial Desired
        Value) field of Motor Record

        Parameters
        ----------
        pos : `double`
            The desired position to set
        waitComplete : `boolean` (default is **False**)
            .. note::
                If **True**, the function will wait until the movement finish
                to return, otherwise don't.
        """

        if(self.getDialRealPosition() == pos):
            return

        self.motor.put('DVAL', pos)
        self._moving = True
        if(waitComplete):
            self.wait()

    def setAbsolutePosition(self, pos, waitComplete=False):
        """
        Move the motor to an absolute position received by an input parameter

        Parameters
        ----------
        pos : `double`
            The desired position to set
        waitComplete : `boolean` (default is **False**)
            .. note::
                If **True**, the function will wait until the movement finish
                to return, otherwise don't.
        """

        if(self.getRealPosition() == pos):
            return

        ret, msg = self.canPerformMovement(pos)
        if(not ret):
            raise Exception("Can't move motor "+self.motorDesc+" ("+self.pvName+") to desired position: "+str(pos)+ ", " + msg)

        self._moving = True
        self.motor.put('VAL',pos)

        ca.poll(evt=0.05)

        if(waitComplete):
            self.wait()

    def setRelativePosition(self, pos, waitComplete=False):
        """
        Move the motor a distance, received by an input parameter, to a position
        relative to that current one

        Parameters
        ----------
        pos : `double`
            The desired distance to move based on current position
        waitComplete : `boolean` (default is **False**)
            .. note:
                If **True**, the function will wait until the movement finish
                to return, otherwise don't.
        """
        if(pos == 0):
            return

        ret, msg = self.canPerformMovement(self.getRealPosition()+pos)
        if(not ret):
            raise Exception("Can't move motor "+self.motorDesc+" ("+
                            self.pvName+") to desired position: "+
                            str(self.getRealPosition()+pos)+ ", " + msg)

        self.motor.put('RLV',pos)

        ca.poll(evt=0.05)

        self._moving = True
        if(waitComplete):
            self.wait()

    def setVelocity(self, velo):
        """
        Set the motor velocity up based on the `VELO` (Velocity, EGU/s) field
        from Motor Record

        Parameters
        ----------
        velo : `double`
            The desired velocity to set
        """

        self.motor.put('VELO',velo)

    def setAcceleration(self, accl):
        """
        Set the motor acceleration time based on the `ACCL` (Seconds to
        Velocity) field from Motor Record

        Parameters
        ----------
        accl : `double`
            The desired acceleration to set
        """

        self.motor.put('ACCL',accl)

    def setUpdateRequest(self,val):
        """
        Set the motor update request flag based on the `STUP` (Status Update
        Request) field from Motor Record

        Parameters
        ----------
        val : `integer`
            The desired value to set for the flag
        """

        self.motor.put('STUP',val)

    def validateLimits(self):
        """
        Verify if motor is in a valid position.  In the case it has been reached
        the HIGH or LOW limit switch, an exception will be raised.
        """
        message = ""
        if(self.isAtHighLimitSwitch()):
            message = 'Motor: '+self.motorDesc+' ('+self.pvName+') reached the HIGH limit switch.'
        elif(self.isAtLowLimitSwitch()):
            message = 'Motor: '+self.motorDesc+' ('+self.pvName+') reached the LOW limit switch.'
        if(message != ""):
            raise Exception(message)

    def canPerformMovement(self, target):
        """
        Check if a movement to a given position is possible using the limit
        values and backlash distance

        Returns
        -------
        `boolean`

        .. note::
            - **True** -- Motor CAN perform the desired movement;
            - **False** -- Motor **CANNOT** perform the desired movement.
        """
        # Moving to high limit
        if(target > self.getRealPosition()):
            if(self.isAtHighLimitSwitch()):
                return False, "Motor at high limit switch"
        # Moving to low limit
        else:
            if(self.isAtLowLimitSwitch()):
                return False, "Motor at low limit switch"

        if(self.getLVIO()==0):
            return True, ""
        
        return False, "Movement beyond soft limits"

    def canPerformMovementCalc(self, target):
        """
        Check if a movement to a given position is possible using the limit
        values and backlash distance calculating the values

        Returns
        -------
        `boolean`

        .. note::
            - **True** -- Motor CAN perform the desired movement;
            - **False** -- Motor **CANNOT** perform the desired movement.
        """
        
        if self.getHighLimitValue() == 0.0 and self.getLowLimitValue() == 0.0:
                return True
        
        backlashCalc = self.calculateBacklash(target)

        if(self.getDirection()==0):
        
            if(backlashCalc > 0):
                vFinal = target - backlashCalc
            else:
                vFinal = target + backlashCalc
        else:
            if(backlashCalc > 0):
                vFinal = target + backlashCalc
            else:
                vFinal = target - backlashCalc

        if(target > self.getRealPosition()):
            if(self.isAtHighLimitSwitch()):
                return False

            if vFinal <= self.getHighLimitValue():
                return True
            
        # Moving to low limit
        else:
            if(self.isAtLowLimitSwitch()):
                return False

            if vFinal >= self.getLowLimitValue():
                return True
            
        return False

    def calculateBacklash(self, target):
        """
        Calculates the backlash distance of a given motor

        Returns
        -------
        `double`

        """

        # Positive Movement
        if(self.getDirection() == 0):
            if(self.getBacklashDistanceValue() > 0 and target < self.getRealPosition()) or (self.getBacklashDistanceValue() < 0 and target > self.getRealPosition()):
                return self.getBacklashDistanceValue()
        else:
            if(self.getBacklashDistanceValue() > 0 and target > self.getRealPosition()) or (self.getBacklashDistanceValue() < 0 and target < self.getRealPosition()):
                return self.getBacklashDistanceValue()
        return 0    

    def stop(self):
        """
        Stop the motor
        """
        self.motor.put('STOP',1)

    def wait(self):
        """
        Wait until the motor movement finishes
        """
        while(self._moving):
            ca.poll(evt=0.01)
            
    def getValue(self):
        """
        Get the current position of the motor.
        See :class:`py4syn.epics.IScannable`

        Returns
        -------
        `double`
            Read the current value (Motor Real Position)
        """

        return self.getRealPosition()
        
    def setValue(self, v):
        """
        Set the desired motor position.
        See :class:`py4syn.epics.IScannable`

        Parameters
        ----------
        v : `double`
            The desired value (Absolute Position) to set 
        """
        self.setAbsolutePosition(v)
Esempio n. 53
0
 def __init__(self, prefix, roistat="ROIStat1:"):
     Device.__init__(self, '%s%s' % (prefix, roistat),
                     delim='', attrs=AD_ROISTAT_ATTRS)
Esempio n. 54
0
 def __init__(self, prefix='13XRM:ION:'):
     Device.__init__(self, prefix, attrs=self.attrs)
Esempio n. 55
0
class ImagePanel_EpicsAD(ImagePanel_Base):
    img_attrs = ('ArrayData', 'UniqueId_RBV', 'NDimensions_RBV',
                 'ArraySize0_RBV', 'ArraySize1_RBV', 'ArraySize2_RBV',
                 'ColorMode_RBV')

    cam_attrs = ('Acquire', 'ArrayCounter', 'ArrayCounter_RBV',
                 'DetectorState_RBV', 'NumImages', 'ColorMode',
                 'ColorMode_RBV', 'DataType_RBV', 'Gain', 'AcquireTime',
                 'AcquirePeriod', 'ImageMode', 'ArraySizeX_RBV',
                 'ArraySizeY_RBV')
    """Image Panel for FlyCapture2 camera"""
    def __init__(self,
                 parent,
                 prefix=None,
                 format='JPEG',
                 writer=None,
                 autosave_file=None,
                 **kws):
        super(ImagePanel_EpicsAD, self).__init__(parent,
                                                 -1,
                                                 size=(800, 600),
                                                 writer=writer,
                                                 autosave_file=autosave_file,
                                                 **kws)

        self.format = format
        self.set_prefix(prefix)
        self.imgcount = 0
        self.imgcount_start = 0
        self.last_update = 0.0

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.onTimer, self.timer)

    def set_prefix(self, prefix):
        if prefix.endswith(':'): prefix = prefix[:-1]
        if prefix.endswith(':image1'): prefix = prefix[:-7]
        if prefix.endswith(':cam1'): prefix = prefix[:-5]
        self.prefix = prefix

        self.ad_img = Device(prefix + ':image1:',
                             delim='',
                             attrs=self.img_attrs)
        self.ad_cam = Device(prefix + ':cam1:', delim='', attrs=self.cam_attrs)

        self.config_filesaver(prefix, self.format)

        w, h = self.GetImageSize()
        self.cam_name = prefix

    def config_filesaver(self, prefix, format):
        if not prefix.endswith(':'): prefix = "%s:" % prefix
        if not format.endswith('1'): format = "%s1" % format
        if not format.endswith('1:'): format = "%s:" % format

        cname = "%s%s" % (prefix, format)
        caput("%sEnableCallbacks" % cname, 1)
        thisdir = os.path.abspath(os.getcwd()).replace('\\', '/')
        # caput("%sFilePath" % cname, thisdir)
        caput("%sAutoSave" % cname, 0)
        caput("%sAutoIncrement" % cname, 0)
        caput("%sFileTemplate" % cname, "%s%s")
        if format.upper() == 'JPEG1:':
            caput("%sJPEGQuality" % cname, 90)

    def Start(self):
        "turn camera on"
        self.timer.Start(50)
        if self.autosave_thread is not None:
            self.autosave_thread.start()

    def Stop(self):
        "turn camera off"
        self.timer.Stop()
        self.autosave = False
        if self.autosave_thread is not None:
            self.autosave_thread.join()

    def SetExposureTime(self, exptime):
        "set exposure time"
        self.ad_cam.AcquireTime = exptime

    def AutoSetExposureTime(self):
        """auto set exposure time"""
        count, IMAX = 0, 255.0
        while count < 8:
            img = self.GrabNumpyImage().astype(np.uint8)
            count += 1
            scale = 0
            if img.max() < 0.5 * IMAX:
                scale = 1.5
            elif img.mean() > 0.50 * IMAX:
                scale = 0.75
            elif img.mean() < 0.20 * IMAX:
                scale = 1.25
            else:
                break
            if scale > 0:
                scale = max(0.2, min(5.0, scale))
                self.ad_cam.AcquireTime *= scale
                time.sleep(0.1)

    def GetImageSize(self):
        arrsize0 = self.ad_img.ArraySize0_RBV
        arrsize1 = self.ad_img.ArraySize1_RBV
        arrsize2 = self.ad_img.ArraySize2_RBV
        self.arrsize = (arrsize0, arrsize1, arrsize2)
        self.colormode = self.ad_img.ColorMode_RBV

        w, h = arrsize0, arrsize1
        if self.ad_img.NDimensions_RBV == 3:
            w, h = arrsize1, arrsize2
        self.img_w = float(w + 0.5)
        self.img_h = float(h + 0.5)
        return w, h

    def GrabNumpyImage(self):
        width, height = self.GetImageSize()

        im_mode = 'L'
        self.im_size = (self.arrsize[0], self.arrsize[1])
        if self.ad_img.ColorMode_RBV == 2:
            im_mode = 'RGB'
            self.im_size = (self.arrsize[1], self.arrsize[2])

        dcount = self.arrsize[0] * self.arrsize[1]
        if self.ad_img.NDimensions_RBV == 3:
            dcount *= self.arrsize[2]

        img = self.ad_img.PV('ArrayData').get(count=dcount)
        if img is None:
            time.sleep(0.025)
            img = self.ad_img.PV('ArrayData').get(count=dcount)

        if self.ad_img.ColorMode_RBV == 2:
            img = img.reshape((width, height, 3)).sum(axis=2)
        else:
            img = img.reshape((width, height))
        return img

    def GrabWxImage(self, scale=1, rgb=True, can_skip=True):
        if self.ad_img is None or self.ad_cam is None:
            print('GrabWxImage .. no ad_img / cam', self.ad_img, self.ad_cam)
            return

        width, height = self.GetImageSize()
        imgcount = self.ad_cam.ArrayCounter_RBV
        now = time.time()
        if (can_skip and (imgcount == self.imgcount
                          or abs(now - self.last_update) < 0.025)):
            return None
        self.imgcount = imgcount
        self.last_update = time.time()

        im_mode = 'L'
        self.im_size = (self.arrsize[0], self.arrsize[1])
        if self.ad_img.ColorMode_RBV == 2:
            im_mode = 'RGB'
            self.im_size = (self.arrsize[1], self.arrsize[2])

        dcount = self.arrsize[0] * self.arrsize[1]
        if self.ad_img.NDimensions_RBV == 3:
            dcount *= self.arrsize[2]

        rawdata = self.ad_img.PV('ArrayData').get(count=dcount)
        if rawdata is None:
            return

        if (self.ad_img.ColorMode_RBV == 0 and isinstance(rawdata, np.ndarray)
                and rawdata.dtype != np.uint8):
            im_mode = 'I'
            rawdata = rawdata.astype(np.uint32)
        if im_mode in ('L', 'I'):
            image = wx.EmptyImage(width, height)
            imbuff = Image.frombuffer(im_mode, self.im_size, rawdata, 'raw',
                                      im_mode, 0, 1)
            image.SetData(imbuff.convert('RGB').tobytes())
        elif im_mode == 'RGB':
            rawdata.shape = (3, width, height)
            rawdata = rawdata.astype(np.uint8)
            if is_wxPhoenix:
                image = wx.Image(width, height, rawdata)
            else:
                image = wx.ImageFromData(width, height, rawdata)
        return image.Scale(int(scale * width), int(scale * height))
Esempio n. 56
0
class OmronE5CK(StandardDevice, IScannable):
    """
    Class to control Omron E5CK temperature controllers via EPICS.

    Examples
    --------
    >>> from py4syn.epics.OmronE5CKClass import OmronE5CK
    >>> 
    >>> def showTemperature(pv='', name=''):
    ...     e5ck = OmronE5CK(pv, name)
    ...     print('Temperature is: %d' % e5ck.getValue())
    ...
    >>> def fastRaiseTemperature(e5ck, amount, rate=30):
    ...     e5ck.setRate(rate)
    ...     e5ck.setValue(e5ck.getValue() + amount)
    ...
    >>> def complexRamp(e5ck):
    ...     e5ck.setRate(10)
    ...     e5ck.setValue(200)
    ...     e5ck.wait()
    ...     e5ck.setRate(2)
    ...     e5ck.setValue(220)
    ...     e5ck.wait()
    ...     sleep(500)
    ...     e5ck.setRate(5)
    ...     e5ck.setValue(100)
    ...     e5ck.wait()
    ...     e5ck.stop()
    ...
    >>> import py4syn
    >>> from py4syn.epics.ScalerClass import Scaler
    >>> from py4syn.utils.counter import createCounter
    >>> from py4syn.utils.scan import scan
    >>> 
    >>> def temperatureScan(start, end, rate, pv='', counter='', channel=2):
    ...     e5ck = OmronE5CK(pv, 'e5ck')
    ...     py4syn.mtrDB['e5ck'] = e5ck
    ...     c = Scaler(counter, channel, 'simcountable')
    ...     createCounter('counter', c, channel)
    ...     e5ck.setRate(rate)
    ...     scan('e5ck', start, end, 10, 1)
    ...     e5ck.stop()
    ...
    """

    STATUS_IS_RUNNING = 1<<7
    PROGRAM_LENGTH = 4
    COMMAND_GET_STEP = '4010000'
    COMMAND_SET_TARGET = '5%02d%04d'
    TARGETS = (5, 8, 11, 14,)
    TIMES = (7, 10, 13, 16,)

    def __init__(self, pvName, mnemonic):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        pvName : `string`
            Power supply base naming of the PV (Process Variable)
        mnemonic : `string`
            Temperature controller mnemonic
        """
        super().__init__(mnemonic)

        self.device = Device(pvName + ':', ['termopar', 'target', 'status', 'stepNum',
                             'programTable', 'programming', 'run', 'stop', 'advance',
                             'setPatternCount', 'timeScale', 'level1', 'reset', 'pause',
                             'sendCommand', 'pidtable', 'numPIDElements', 'paused', 'getP',
                             'getI', 'getD', 'power'])

        self.programmingDone = Event()
        self.newTemperature = Event()
        self.newStep = Event()
        self.device.add_callback('programming', self.onProgrammingChange)
        self.device.add_callback('termopar', self.onTemperatureChange)
        self.device.add_callback('stepNum', self.onStepChange)
        self.timeScaleCache = self.device.get('timeScale')

        self.pvName = pvName
        self.rate = 5
        self.presetDone = False

    def __str__(self):
        return '%s (%s)' % (self.getMnemonic(), self.pvName)

    def isRunning(self):
        """
        Returns true if the controller is in program mode. Whenever it is program mode,
        it is following a target temperature.

        Returns
        -------
        `bool`
        """
        v = self.device.get('status')
        r = not bool(int(v) & self.STATUS_IS_RUNNING)
        if not r:
            self.presetDone = False

        return r

    def isPaused(self):
        """
        Returns true if the controller is paused (keep temperature).

        Returns
        -------
        `bool`
        """
        paused = self.device.get('paused')

        return paused

    def getValue(self):
        """
        Returns the current measured temperature.

        Returns
        -------
        `float`
        """
        return self.device.get('termopar')

    def getTarget(self):
        """
        Returns the current target temperature. If the device is running, the target
        temperature is the temperature the device is changing to. If the device is not
        running, the target temperature is ignored.

        Returns
        -------
        `float`
        """
        return self.device.get('target')

    def getRealPosition(self):
        """
        Returns the same as :meth:`getValue`.

        See: :meth:`getValue`

        Returns
        -------
        `float`
        """
        return self.getValue()

    def getStepNumber(self):
        """
        Helper method to get the current program step.

        Returns
        -------
        `int`
        """
        return self.device.get('stepNum')

    def getLowLimitValue(self):
        """
        Returns the controller low limit temperature.

        Returns
        -------
        `float`
        """
        return 0.0

    def getHighLimitValue(self):
        """
        Returns the controller high limit temperature.

        Returns
        -------
        `float`
        """
        return 1300.0

    def onProgrammingChange(self, value, **kwargs):
        """
        Helper callback that tracks when the IOC finished programming the device.
        """
        self.presetDone = False
        if value == 0:
            self.programmingDone.set()

    def onStepChange(self, value, **kwargs):
        """
        Helper callback that indicates when a new program step has been reached
        """
        self.newStep.set()

    def onTemperatureChange(self, value, **kwargs):
        """
        Helper callback that indicates when the measured temperature has changed
        """
        self.newTemperature.set()

    def stop(self):
        """
        Stops executing the current temperature program and puts the device in idle
        state. In the idle state, the device will not try to set a target temperature.
        """
        self.device.put('stop', 1)
        self.presetDone = False

    def run(self):
        """
        Starts or resumes executing the current temperature program.
        """
        self.device.put('run', 1)

    def advance(self):
        """
        Helper method to skip the current program step and execute the next one.
        """
        self.device.put('advance', 1)

    def pause(self):
        """
        Pauses current ramp program. To resume program, use :meth:`run`

        See: :meth:`run`
        """
        self.device.put('pause', 1)

    def sendCommand(self, command):
        """
        Helper method to send a custom command to the controller.

        Parameters
        ----------
        command : `str`
            The command to be send
        """
        self.device.put('sendCommand', command.encode(), wait=True)

    def preset(self):
        """
        Makes the controler enter a well defined known state. This method creates and
        runs an "empty" ramp program. The program simply mantains the current
        temperature forever, whatever that temperature is. This is mostly a helper
        function, to allow making complex temperature ramps starting from a known
        state and reusing the preset values.

        .. note::
            Running a new program requires stopping the current program. While the
            program is stopped, the controller power generation drops to zero. Because
            of this power drop, this method may be slow to stabilize.
        """
        self.stop()
        current = self.getValue()

        # Steps 0 and 2 are fake steps, steps 1 and 3 are the real ones.
        # The fake steps are used for synchronizing with the device.
        program = [self.PROGRAM_LENGTH] + self.PROGRAM_LENGTH*[current, 99]
        self.programmingDone.clear()
        self.device.put('setPatternCount', 9999)
        self.device.put('programTable', array(program))
        ca.flush_io()
        self.programmingDone.wait(10)
        self.run()
        self.presetDone = True

    def program(self, programTable):
        """
        Set a programTable to the furnace
        """
        self.programmingDone.clear()
        self.device.put('programTable', array(programTable))
        ca.flush_io()
        self.programmingDone.wait(10)

    def setPIDTable(self, pidTable):
        """
        Set a PIDtable to the furnace
        """
        self.device.put('pidtable', array(pidTable))
    
    def getPIDTable(self):
        """
        Return the current PID table at the furnace

        Returns
        -------
        `array`
        """
        pidTablePV = self.device.PV('pidtable')
        
        return pidTablePV.get()

    def getP(self):
        """
        Return the current P value at the furnace

        Returns
        -------
        `double`
        """
        getPV = self.device.PV('getP')
        
        return getPV.get()

    def getI(self):
        """
        Return the current I value at the furnace

        Returns
        -------
        `double`
        """
        getPV = self.device.PV('getI')
        
        return getPV.get()

    def getD(self):
        """
        Return the current D value at the furnace

        Returns
        -------
        `double`
        """
        getPV = self.device.PV('getD')
        
        return getPV.get()

    def getPower(self):
        """
        Return the current Power value at the furnace

        Returns
        -------
        `double`
        """
        getPV = self.device.PV('power')
        
        return getPV.get()

    def getNumPIDElements(self):
        """
        Return the number of all parameters at a PID table
        
        Returns
        -------
        `int`
        """
        numPIDElementsPV = self.device.PV('numPIDElements')
        
        return numPIDElementsPV.get()

    def getTimeScale(self):
        """
        Returns the time scale being used by the controller. The timescale can either
        be zero, for hours:minutes, or one, for minutes:seconds.

        Returns
        -------
        `int`
        """
        t = self.device.PV('timeScale')
        v = t.get()
        t.get_ctrlvars()
        if t.severity == 0:
            self.timeScaleCache = v

        return self.timeScaleCache

    def setTimeScale(self, minutes):
        """
        Changes the time scale being used by the controller. The timescale can either
        be zero, for hours:minutes, or one, for minutes:seconds. This operation requires
        switching the controller operation mode to be successful, and then a reset is
        issued after it. The whole operation takes more than 5 seconds.

        Parameters
        ----------
        minutes : `int`
            Set to 1 for minutes:seconds, or 0 for hours:minutes
        """
        if minutes == self.getTimeScale() and self.device.PV('timeScale').severity == 0:
            return

        t = self.getValue()

        self.device.put('level1', 1)
        self.device.put('timeScale', minutes)
        self.device.put('reset', 1)

    def getStepNumberSync(self):
        """
        Helper module to retrieve an up-to-date value for the current program step
        number. Similar to :meth:`getStepNumber`, but it doesn't rely on monitor value
        and instead does a synchronous caget() call.

        See: :meth:`getStepNumber`

        Returns
        -------
        `int`
        """
        self.device.put('stepNum.PROC', 0, wait=True)
        v = self.device.PV('stepNum').get(use_monitor=False)
        return int(v)

    def synchronizeStep(self, current):
        """
        Helper method to set up a constant temperature right before running a ramp
        program. This method detects if a current ramp program is running or not. If
        it's not, then it doesn't do anything. If there is a ramp running, then it
        configures and advances to a "synchronization step", that is, a step where
        the temperature does not change. This step marks the beginning of the new
        ramp.

        The method returns the resulting step number

        Parameters
        ----------
        current : `float`
            The temperature target for the synchronization step

        Returns
        -------
        `int`
        """

        # This method uses the advance() call to skip steps. Suprisingly, advancing
        # steps with E5CK is not trivial. The reason for this is that E5CK quickly
        # acknowledges the advance command, but delays to actually advance. Ignoring
        # this deficiency results in other commands issued later doing the wrong thing.
        # In particular, calling advance again later may silently fail. We work around
        # this by using a synchronous call to get the current step number and a busy
        # wait to check when the step was really changed.
        #
        # To make things worse, some component in EPICS seems to break serialization by
        # not respecting the order which PVs are updated, so it's not possible to
        # change the program using two separate PVs, like, for example, stepNumConfig
        # setStepTarget, which are implemented in E5CK's IOC. Because of that, a custom
        # PV was added in the IOC to support arbitrary commands sent in a serialized
        # way. This sendCommand procedure is what this method uses.
        step = self.getStepNumberSync()
        while step % 2 == 1:
            target = self.TARGETS[(step+1)%self.PROGRAM_LENGTH]
            self.sendCommand(self.COMMAND_SET_TARGET % (target, current))
            self.advance()

            # E5CK is slow, so loop until it changes state. This is required: calling
            # advance twice in a row doesn't work. A state transition must happen first.
            old = step
            while old == step:
                step = self.getStepNumberSync()
        assert step % 2 == 0

        return step

    def timeToValue(self, t):
        """
        Helper method to convert between minutes to the format used by the controller.

        Parameters
        ----------
        t : `float`
            The desired time, in minutes

        Returns
        -------
        `float`
        """
        if self.getTimeScale() == 0:
            minutes = int(t)%60
            hours = int(t)//60
            value = 100*hours + minutes

            if hours > 99:
                raise OverflowError('Ramp time is too large: %g' % t)
        else:
            minutes = int(t)

            if minutes > 99:
                raise OverflowError('Ramp time is too large with current settings: %g' %
                                    t)

            seconds = min(round((t-minutes)*60), 59)
            value = 100*minutes + seconds

        return value

    def setRate(self, r):
        """
        Sets the ramp speed in degrees per minutes for use with :meth:`setValue`. This
        method does not send a command to the controller, it only stores the rate for
        the next ramps.

        See: :meth:`setValue`

        Parameters
        ----------
        r : `float`
            Ramp speed in °C/min
        """
        self.rate = r

    def setVelocity(self, velo):
        """
        Same as :meth:`setRate`.

        See: :meth:`setRate`

        Parameters
        ----------
        r : `float`
            Ramp speed in °C/min
        """
        self.setRate(velo)

    def setValue(self, v):
        """
        Changes the temperature to a new value. This method calls preset if it has not
        already been called first. The speed that the new temperature is reached is set
        with :meth:`setRate`. The default rate is 5 °C/minute.

        See: :meth:`setRate`

        Parameters
        ----------
        v : `float`
            The target temperature in °C
        """

        # This method depends on a program preset being loaded and the program being
        # in a synchronization step. Given the two precondition, this method simply
        # programs a ramp, a synchronization step after the ramp and advances to the
        # ramp step.
        if not self.presetDone:
            self.preset()

        # We accept float as input, but the controller is integer only
        v = round(v)

        current = self.getValue()
        minutes = abs(v-current)/self.rate
        time = self.timeToValue(minutes)

        step = self.synchronizeStep(current)
        self.waitStep = (step+2)%self.PROGRAM_LENGTH
        x = self.TARGETS[step+1]
        y = self.TIMES[step+1]
        z = self.TARGETS[self.waitStep]
        self.sendCommand(self.COMMAND_SET_TARGET % (x, v))
        self.sendCommand(self.COMMAND_SET_TARGET % (y, time))
        self.sendCommand(self.COMMAND_SET_TARGET % (z, v))
        self.advance()
        self.valueTarget = v

    def wait(self):
        """
        Blocks until the requested temperature is achieved.
        """
        if not self.presetDone:
            return

        # Waiting is done in two steps. First step waits until the program reaches
        # the next synchronization step. Second step waits util the measured temperature
        # reaches the requested temperature
        self.newStep.clear()
        while self.getStepNumber() != self.waitStep:
            ca.flush_io()
            self.newStep.wait(60)
            self.newStep.clear()

        self.newTemperature.clear()
        while self.getValue() != self.valueTarget:
            ca.flush_io()

            # Safety timeout, temperature didn't change after a long time
            if not self.newTemperature.wait(120):
                return

            self.newTemperature.clear()
Esempio n. 57
0
class Keithley6485(Keithley6514):
    """

    Python class to help configuration and control the Keithley 6514 Electrometer.

    Keithley is an electrical instrument for measuring electric charge or electrical potential difference.
    This instrument is capable of measuring extremely low currents. E.g.: pico (10e-12), i.e.: 0,000 000 000 001.

    For more information, please, refer to: `Model 6514 System Electrometer Instruction Manual <http://www.tunl.duke.edu/documents/public/electronics/Keithley/keithley-6514-electrometer-manual.pdf>`_
    """
    def __init__(self, pvName, mnemonic, timeBased=False):
        """
        **Constructor**
        To use this Keithley Class you must pass the PV (Process Variable) prefix.

            .. Note::
                e.g.: SXS:K6514

        Examples
        --------

        >>> from KeithleyClass import *
        >>> name = Keithley('SOL:K6514', 'k1')

        """
        Keithley6514.__init__(self, pvName, mnemonic)
        self.pvName = pvName
        self.timeBased = timeBased
        self.keithley = Device(
            pvName + ':',
            ('GetMed', 'SetMed', 'GetMedRank', 'SetMedRank', 'GetAver',
             'SetAver', 'GetAverCoun', 'SetAverCoun', 'GetNPLC', 'SetNPLC',
             'GetAutoZero', 'SetAutoZero', 'GetZeroCheck', 'SetZeroCheck',
             'GetAverTCon', 'SetAverTCon', 'GetRange', 'SetRange',
             'GetZeroCor', 'SetZeroCor', 'GetAutoCurrRange', 'SetAutoCurrRange'
             'Count', 'ContinuesMode', 'CNT', 'OneMeasure'))

        self.pvMeasure = PV(pvName + ':' + 'Measure', auto_monitor=False)
        self._counting = self.isCountingPV()
        self.keithley.add_callback('CNT', self.onStatusChange)

    def getCurrentRange(self):
        """
        Get the value of range.
        Default: Auto range.

        Returns
        -------
        Value: Integer, i.e.: 
        0 (Undefined ZERO), 1 (Indefined UM), 2 (20 mA), 3 (2 mA), 4 (200 uA), 5 (20 uA), 6 (2 uA), 7 (200 nA), 8 (20 nA), 9 (2 nA), 10 (200 pA), 11 (20 pA).
    
        Examples
        --------
        >>> name.getCurrentRange()
        >>> 11
        """

        return self.keithley.get('GetRange')

    def setCurrentRange(self, curange):
        """
        Set the range.

        Parameters
        ----------
        Value: Integer, i.e.: 
        0 (Undefined ZERO), 1 (Indefined UM), 2 (20 mA), 3 (2 mA), 4 (200 uA), 5 (20 uA), 6 (2 uA), 7 (200 nA), 8 (20 nA), 9 (2 nA), 10 (200 pA), 11 (20 pA).

        Examples
        --------
        >>> name.setCurrentRange(5)
        """
        if (curange < 0 or curange > 11):
            raise ValueError('Invalid number - It should be 0 to 11')
        self.keithley.put('SetRange', curange)
Esempio n. 58
0
class Keithley6485(Keithley6514):
    """

    Python class to help configuration and control the Keithley 6514 Electrometer.

    Keithley is an electrical instrument for measuring electric charge or electrical potential difference.
    This instrument is capable of measuring extremely low currents. E.g.: pico (10e-12), i.e.: 0,000 000 000 001.

    For more information, please, refer to: `Model 6514 System Electrometer Instruction Manual <http://www.tunl.duke.edu/documents/public/electronics/Keithley/keithley-6514-electrometer-manual.pdf>`_
    """

    def __init__(self, pvName, mnemonic, timeBased=False):
        """
        **Constructor**
        To use this Keithley Class you must pass the PV (Process Variable) prefix.

            .. Note::
                e.g.: SXS:K6514

        Examples
        --------

        >>> from KeithleyClass import *
        >>> name = Keithley('SOL:K6514', 'k1')

        """
        StandardDevice.__init__(self, mnemonic)
        self.pvName = pvName
        self.timeBased = timeBased
        self.keithley = Device(
            pvName + ":",
            (
                "GetMed",
                "SetMed",
                "GetMedRank",
                "SetMedRank",
                "GetAver",
                "SetAver",
                "GetAverCoun",
                "SetAverCoun",
                "GetNPLC",
                "SetNPLC",
                "GetAutoZero",
                "SetAutoZero",
                "GetZeroCheck",
                "SetZeroCheck",
                "GetAverTCon",
                "SetAverTCon",
                "GetRange",
                "SetRange",
                "GetZeroCor",
                "SetZeroCor",
                "GetAutoCurrRange",
                "SetAutoCurrRange" "Count",
                "ContinuesMode",
                "CNT",
                "OneMeasure",
            ),
        )

        self.pvMeasure = PV(pvName + ":" + "Measure", auto_monitor=False)
        self._counting = self.isCountingPV()
        self.keithley.add_callback("CNT", self.onStatusChange)

    def getCurrentRange(self):
        """
        Get the value of range.
        Default: Auto range.

        Returns
        -------
        Value: Integer, i.e.: 
        0 (Undefined ZERO), 1 (Indefined UM), 2 (20 mA), 3 (2 mA), 4 (200 uA), 5 (20 uA), 6 (2 uA), 7 (200 nA), 8 (20 nA), 9 (2 nA), 10 (200 pA), 11 (20 pA).
    
        Examples
        --------
        >>> name.getCurrentRange()
        >>> 11
        """

        return self.keithley.get("GetRange")

    def setCurrentRange(self, curange):
        """
        Set the range.

        Parameters
        ----------
        Value: Integer, i.e.: 
        0 (Undefined ZERO), 1 (Indefined UM), 2 (20 mA), 3 (2 mA), 4 (200 uA), 5 (20 uA), 6 (2 uA), 7 (200 nA), 8 (20 nA), 9 (2 nA), 10 (200 pA), 11 (20 pA).

        Examples
        --------
        >>> name.setCurrentRange(5)
        """
        if curange < 0 or curange > 11:
            raise ValueError("Invalid number - It should be 0 to 11")
        self.keithley.put("SetRange", curange)