Пример #1
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)
Пример #2
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)
Пример #3
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()
Пример #4
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)
Пример #5
0
class Lauda(StandardDevice, IScannable):
    """
    Class to control Lauda temperature controllers via EPICS.

    Examples
    --------
    >>> from py4syn.epics.LaudaClass import Lauda
    >>>    
    >>> def showTemperature(pv):
    ...    lauda = Lauda(pv, 'lauda')
    ...    print('Temperature is: %d' % lauda.getValue())
    ...
    >>> def setTemperature(lauda, temperature):
    ...    lauda.setValue(temperature)
    ...    lauda.run()
    """

    EPSILON = 0.1

    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)

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

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

        Returns
        -------
        `int`
        """
        return self.lauda.get('BTEMP')

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

        See: :meth:`getValue`

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

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

    def setVelocity(self, r):
        """
        Dummy method setVelocity()

        Parameters
        ----------
        r : `float`
            Ramp speed in °C/min
        """
        pass

    def setValue(self, v):
        """
        Changes the temperature to a new value.

        Parameters
        ----------
        v : `int`
            The target temperature in °C
        """
        self.lauda.put('WSP', v)
        self.run()
        self.requestedValue = v

    def wait(self):
        """
        Blocks until the requested temperature is achieved.
        """

        ca.flush_io()
        self.newTemperature.clear()

        while abs(self.getValue() - self.requestedValue) > self.EPSILON:
            # Give up after 60 seconds without an update
            if not self.newTemperature.wait(60):
                break

            self.newTemperature.clear()

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

        Returns
        -------
        `int`
        """
        return -20

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

        Returns
        -------
        `int`
        """
        return 200

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

    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.lauda.put('WSTOP', 1)

    def setPumpSpeed(self, speed):
        """
        Changes the pump speed.

        Parameters
        ----------
        speed : `int`
            The requested pump speed, ranging from 1 to 8.
        """

        if speed < 1 or speed > 8:
            raise ValueError('Invalid speed')

        self.lauda.put('WPUMP', speed)

    def getInternalTemp(self):
        """
        Same as :meth:`getValue`.

        See :meth:`getValue`

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

    def getExternalTemp(self):
        """
        Returns the device's external temperature.

        Returns
        -------
        `int`
        """
        return self.lauda.get('ETEMP')

    def getLevel(self):
        """
        Returns the device's bath level.

        Returns
        -------
        `int`
        """
        return self.lauda.get('BLEVEL')

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

        Returns
        -------
        `int`
        """
        return self.lauda.get('BSP')

    def getPower(self):
        """
        Returns the current device power.

        Returns
        ----------
        `int`
        """
        return self.lauda.get('BPOWER')

    def getOverTemp(self):
        """
        Returns the maximum temperature software defined limit.

        Returns
        ----------
        `int`
        """
        return self.lauda.get('BOVERTEMP')

    def getTN(self):
        """
        Returns
        ----------
        `int`
        """
        return self.lauda.get('BTN')

    def getStatus(self):
        """
        Returns the device status word.

        Returns
        ----------
        `int`
        """
        return self.lauda.get('BSTATS')

    def getThermoStatus(self):
        """
        Returns the device thermostat error word.

        Returns
        ----------
        `int`
        """
        return self.lauda.get('BTHERMOSTATS')

    def changeSetPoint(self, val):
        """
        Same as :meth:`setValue`.

        See :meth:`setValue`

        Parameters
        ----------
        val : `int`
            The requested temperature.
        """
        self.setValue(val)

    def changePump(self, val):
        """
        Same as :meth:`setPumpSpeed`.

        See :meth:`setPumpSpeed`

        Parameters
        ----------
        val : `int`
            The requested pump speed.
        """
        self.setPumpSpeed(val)

    def changeTN(self, val):
        self.lauda.put('WTN', val)

    def start(self):
        """
        Same as :meth:`run`.

        See :meth:`run`
        """
        self.run()
Пример #6
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)
Пример #7
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)
Пример #8
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 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()

    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, wait=True)

    def getSETMode(self):
        """
        Checks if the motor is in SET mode

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

        return self.motor.PV('SET').get(use_monitor=False) == 1

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

        self.motor.put('SUSE', 1, wait=True)

    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.
        """
        target = self.getRealPosition() + pos
        self.setAbsolutePosition(target, waitComplete=waitComplete)

    def getVelocity(self):
        """
        Get the motor velocity based on the `VELO` (Velocity, EGU/s) field
        from Motor Record
        
        Returns
        -------
        `double`
        """

        return self.motor.get('VELO')

    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 getAcceleration(self):
        """
        Get the motor acceleration time based on the `ACCL` (Seconds to
        Velocity) field from Motor Record
        
        Returns
        -------
        `double`
        """

        return self.motor.get('ACCL')

    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.
        """
        if self.getSETMode():
            return True, ""

        # Moving to high limit
        if (target > self.getRealPosition()):
            if (self.isAtHighLimitSwitch()):
                return False, "Motor at hardware high limit switch"
            elif target > self.getHighLimitValue():
                return False, "Target beyond value for software high limit."
        # Moving to low limit
        else:
            if (self.isAtLowLimitSwitch()):
                return False, "Motor at hardware low limit switch"
            elif target < self.getLowLimitValue():
                return False, "Target beyond value for software low limit."

        return True, ""

    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.getSETMode():
            return True

        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)

    def homeForward(self):
        """
        Move the motor until it hits the forward limit switch or the software limit.
        """
        self._moving = True
        self.motor.put('HOMF', 1)

    def homeReverse(self):
        """
        Move the motor until it hits the reverse limit switch or the software limit.
        """
        self._moving = True
        self.motor.put('HOMR', 1)
Пример #9
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)
Пример #10
0
class LinkamCI94(StandardDevice, IScannable):
    """
    Class to control Linkam CI94 temperature controllers via EPICS.

    Examples
    --------
    >>> from py4syn.epics.LinkamCI94Class import LinkamCI94
    >>> 
    >>> def showTemperature(pv='', name=''):
    ...     ci94 = LinkamCI94(pv, name)
    ...     print('Temperature is: %d' % ci94.getValue())
    ...
    >>> def fastRaiseTemperature(ci94, amount, rate=30):
    ...     ci94.setRate(rate)
    ...     ci94.setValue(ci94.getValue() + amount)
    ...
    >>> def complexRamp(ci94):
    ...     ci94.setPumpSpeed(-1)
    ...     ci94.setRate(10)
    ...     ci94.setValue(200)
    ...     ci94.wait()
    ...     ci94.setRate(2)
    ...     ci94.setValue(220)
    ...     ci94.wait()
    ...     sleep(500)
    ...     ci94.setRate(5)
    ...     ci94.setValue(100)
    ...     ci94.wait()
    ...     ci94.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):
    ...     ci94 = LinkamCI94(pv, 'ci94')
    ...     py4syn.mtrDB['ci94'] = ci94
    ...     c = Scaler(counter, channel, 'simcountable')
    ...     createCounter('counter', c, channel)
    ...     ci94.setRate(rate)
    ...     scan('ci94', start, end, 10, 1)
    ...     ci94.stop()
    ...
    """

    STATUS_STOPPED = 0
    PUMP_AUTOMATIC = 0
    PUMP_MANUAL = 1

    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)

    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.linkam.get('status')
        return v != self.STATUS_STOPPED

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

        Returns
        -------
        `float`
        """
        return self.linkam.get('temp')

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

        See: :meth:`getValue`

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

    def onPendingChange(self, value, **kwargs):
        """
        Helper callback that tracks when the IOC finished changing
        to a requested temperature.
        """
        self.pending = bool(value)
        if not self.pending:
            self.done.set()

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

    def setRate(self, r):
        """
        Sets the ramp speed in degrees per minutes for use with :meth:`setValue`.

        See: :meth:`setValue`

        Parameters
        ----------
        r : `float`
            Ramp speed in °C/min
        """
        self.linkam.put('setRate', 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. 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
        """
        self.done.clear()
        self.pending = True
        self.requestedValue = v
        self.linkam.put('setLimit', v)

        if not self.isRunning():
            self.run()

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

        # Waiting is done in two steps. First step waits until the IOC deasserts
        # the pending flag to indicate a complete operation. Second step waits util the
        # measured temperature reaches the requested temperature.
        ca.flush_io()
        self.done.wait()

        self.newTemperature.clear()
        timeout = Timer(7)
        while self.getValue() != self.requestedValue and timeout.check():
            self.newTemperature.wait(1)
            self.newTemperature.clear()

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

        Returns
        -------
        `float`
        """
        return -196

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

        Returns
        -------
        `float`
        """
        return 1500

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

    def stop(self):
        """
        Stops executing the current temperature program, stops the nitrogen pump and puts
        the device in idle state. In the idle state, the device will not try to set a
        target temperature.
        """
        self.setPumpSpeed(0)
        self.linkam.put('stop', 1)

    def setPumpSpeed(self, speed):
        """
        Changes the nitrogen pump speed, or enables automatic pump speed control.

        .. note::
            The Linkam front panel only has 5 LEDs to indicate speed, but internally
            it supports 30 different speed levels.

        Parameters
        ----------
        speed : `int`
            The requested pump speed, ranging from 0 (pump off) to 30 (pump top speed),
            or -1 to enable automatic pump control.
        """

        if speed < -1 or speed > 30:
            raise ValueError('Invalid speed')

        if speed == -1:
            self.linkam.put('pumpMode', self.PUMP_AUTOMATIC)
            return

        self.linkam.put('pumpMode', self.PUMP_MANUAL, wait=True)
        self.linkam.put('setSpeed', speed)
Пример #11
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)
Пример #12
0
class Hexapode(IScannable, StandardDevice):
    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:'******'POSUSER:'******'STATE#PANEL:SET', 11)
        self.hexapode.put('MOVE#PARAM:' + self.axis, self.pos)
        self.moving = True
        self.wait()

    def wait(self):
        while self.moving:
            pass

        self.stop()

    def stop(self):
        self.hexapode.put('STATE#PANEL:SET', 0)
        self.moving = False

#------------------------------------------------------------------------------------------

    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.
        
        """
        if (self.hexapode.get('STATE#POSVALID?') > 0):
            return False, "Out of SYMETRIE workspace."

    def getLimits(self, coord, axis=0):  #32 and #33
        #Coord: 0- Machine, 1- users
        #Axis: 0-All, 1-X, 2-Y, 3-Z, 4-RX, 5-RY, 6-RZ

        if (coord != 0 and coord != 1):
            raise ValueError(
                "Invalid value for coord argument. It should be 0 or 1")

        elif (axis < 0 or axis > 6):
            raise ValueError(
                "Invalid value for axis argument. It should be between 1 and 6"
            )

        else:
            stateValue = 32
            if (coord == 1):
                stateValue = 33

            self.hexapode.put('STATE#PANEL:SET', stateValue)
            if (axis > 0):
                negLim = self.hexapode.get('CFG#CS?:' + str(2 * axis - 1))
                posLim = self.hexapode.get('CFG#CS?:' + str(2 * axis))
                return negLim, posLim
            else:
                negLimX = self.hexapode.get('CFG#CS?:1')
                posLimX = self.hexapode.get('CFG#CS?:2')
                negLimY = self.hexapode.get('CFG#CS?:3')
                posLimY = self.hexapode.get('CFG#CS?:4')
                negLimZ = self.hexapode.get('CFG#CS?:5')
                posLimZ = self.hexapode.get('CFG#CS?:6')
                negLimRX = self.hexapode.get('CFG#CS?:7')
                posLimRX = self.hexapode.get('CFG#CS?:8')
                negLimRY = self.hexapode.get('CFG#CS?:9')
                posLimRY = self.hexapode.get('CFG#CS?:10')
                negLimRZ = self.hexapode.get('CFG#CS?:11')
                posLimRZ = self.hexapode.get('CFG#CS?:12')
                enabledLimits = self.hexapode.get('CFG#CS?:13')

                return (negLimX, posLimX, negLimY, posLimY, negLimZ, posLimZ,
                        negLimRX, posLimRX, negLimRY, posLimRY, negLimRZ,
                        posLimRZ, enabledLimits)

    def getLowLimitValue(self):
        if (self.axis_number < 0 or self.axis_number > 6):
            raise ValueError(
                "Invalid value for axis argument. It should be between 1 and 6"
            )

        else:
            stateValue = 33
            self.hexapode.put('STATE#PANEL:SET', stateValue)
            if (self.axis_number > 0):
                negLim = self.hexapode.get('CFG#CS?:' +
                                           str(2 * self.axis_number - 1))
                posLim = self.hexapode.get('CFG#CS?:' +
                                           str(2 * self.axis_number))
                return negLim  #, posLim
            else:
                print("Error getLowLimitValue")

    def getHighLimitValue(self):
        if (self.axis_number < 0 or self.axis_number > 6):
            raise ValueError(
                "Invalid value for axis argument. It should be between 1 and 6"
            )

        else:
            stateValue = 33
            self.hexapode.put('STATE#PANEL:SET', stateValue)
            if (self.axis_number > 0):
                negLim = self.hexapode.get('CFG#CS?:' +
                                           str(2 * self.axis_number - 1))
                posLim = self.hexapode.get('CFG#CS?:' +
                                           str(2 * self.axis_number))
                return posLim
            else:
                print("Error getHighLimitValue")

    def getDialLowLimitValue(self):
        if (self.axis_number < 0 or self.axis_number > 6):
            raise ValueError(
                "Invalid value for axis argument. It should be between 1 and 6"
            )

        else:
            stateValue = 32
            self.hexapode.put('STATE#PANEL:SET', stateValue)
            if (self.axis_number > 0):
                negLim = self.hexapode.get('CFG#CS?:' +
                                           str(2 * self.axis_number - 1))
                return negLim
            else:
                print("Error getDialLowLimitValue")

    def getDialHighLimitValue(self):
        if (self.axis_number < 0 or self.axis_number > 6):
            raise ValueError(
                "Invalid value for axis argument. It should be between 1 and 6"
            )

        else:
            stateValue = 32
            self.hexapode.put('STATE#PANEL:SET', stateValue)
            if (self.axis_number > 0):
                posLim = self.hexapode.get('CFG#CS?:' +
                                           str(2 * self.axis_number))
                return posLim
            else:
                print("Error getDialHLimit")

#-------------------Motors-----

    def isMoving(self):
        return self.moving

    def getRealPosition(self):
        return self.getValue()

    def getDialRealPosition(self):
        return self.hexapode.get('POSMACH:' + self.axis)

    def validateLimits(self):
        return self.getLimits(0)

    def setRelativePosition(self, pos, waitComplete=False):
        target = self.getRealPosition() + pos
        self.setAbsolutePosition(target, waitComplete=waitComplete)
Пример #13
0
class Keithley6514(StandardDevice, ICountable):
    """

    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 onStatusChange(self, value, **kw):
        self._counting = (value == 1)

    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)

    def isCountingPV(self):
        return (self.keithley.get('CNT') == 1)

    def isCounting(self):
        return self._counting

    def wait(self):
        while (self.isCounting()):
            ca.poll(0.00001)

    def getTriggerReading(self):
        """
        Trigger and return reading(s).

        Returns
        -------
        Value: Float, e.g.: -6.0173430000000003e-16.

        Examples
        --------
        >>> name.getTriggerReading()
        >>> -1.0221850000000001e-15
        """
        return self.pvMeasure.get(use_monitor=False)
        #return self.keithley.get('Measure')

    def getCountNumberReading(self):
        """
        Count the number of reading(s).

        Returns
        -------
        Value: Integer, e.g.: 963.

        Examples
        --------
        >>> name.CountNumberReading()
        >>> 161.0
        """

        return self.keithley.get('Count')

    def getStatusContinuesMode(self):
        """
        Get the status of Continues Mode (enable/disable).
        Default: enable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.getStatusContinuesMode()
        >>> True
        """

        return bool(self.keithley.get('ContinuesMode'))

    def setStatusContinuesMode(self, cmode):
        """
        Set enable/disable to continues mode. Let this enable if you want a continuing measuring.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.setStatusContinuesMode(0)
        """

        if (cmode != 0 and cmode != 1):
            raise ValueError('Invalid number - It should be 0 or 1')
        self.keithley.put('ContinuesMode', cmode)

    def getAutoZeroing(self):
        """
        Get the status of Auto Zero (enable/disable).
        Default: enable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.getAutoZeroing()
        >>> True
        """

        return bool(self.keithley.get('GetAutoZero'))

    def setAutoZeroing(self, autozero):
        """
        Set enable/disable for Auto Zero.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.setAutoZeroing(1)
        """

        if (autozero != 0 and autozero != 1):
            raise ValueError('Invalid number - It should be 0 or 1')
        self.keithley.put('SetAutoZero', autozero)

    def getMedianFilter(self):
        """
        Get the status of Median Filter (enable/disable).
        Default: enable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.getMedianFilter()
        >>> True
        """

        return bool(self.keithley.get('GetMed'))

    def setMedianFilter(self, med):
        """
        Set enable/disable for Median Filter.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.setMedianFilter(1)
        """

        if (med != 0 and med != 1):
            raise ValueError('Invalid number - It should be 0 or 1')
        self.keithley.put('SetMed', med)

    def getMedianRank(self):
        """
        Get the value of Median Rank, this number of sample readings are between 1 to 5.
        Default: 5.

        Returns
        -------
        Value: Integer, i.e.: 1 to 5.

        Examples
        --------
        >>> name.getMedianRank()
        >>> 5.0
        """

        return self.keithley.get('GetMedRank')

    def setMedianRank(self, medrank):
        """
        Set the number of sample readings used for the median calculation.

        Parameters
        ----------
        Value: Integer, i.e.: 1 to 5.

        Examples
        --------
        >>> name.setMedianRank(3)
        """

        if (medrank < 1 or medrank > 5):
            raise ValueError('Invalid number - It should be 1 to 5')
        self.keithley.put('SetMedRank', medrank)

    def getAverageDigitalFilter(self):
        """
        Get the status of Digital Filter (enable/disable).
        Default: enable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.getAverageDigitalFilter()
        >>> True
        """

        return bool(self.keithley.get('GetAver'))

    def setAverageDigitalFilter(self, aver):
        """
        Set enable/disable for Digital Filter.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.setAverageDigitalFilter(1)
        """

        if (aver != 0 and aver != 1):
            raise ValueError('Invalid number - It should be 0 or 1')
        self.keithley.put('SetAver', aver)

    def getAverageCount(self):
        """
        Get the number of filter count.
        Default: 10.

        Returns
        -------
        Value: Integer, i.e.: 2 to 100.

        Examples
        --------
        >>> name.getAverageCount()
        >>> 10.0
        """

        return self.keithley.get('GetAverCoun')

    def setAverageCount(self, avercoun):
        """
        Set the number of filter count.

        Parameters
        ----------
        Value: Integer, i.e.: 2 to 100.

        Examples
        --------
        >>> name.setAverageCount(80)
        """

        if (avercoun < 2 or avercoun > 100):
            raise ValueError('Invalid number - It should be 2 to 100')
        self.keithley.put('SetAverCoun', avercoun)

    def getIntegrationTime(self):
        """
        Get the number of integration rate.
        Default: 1.

        Returns
        -------
        Value: Float, i.e.: 0.01 to 10 (PLCs). Where 1 PLC for 60Hz is 16.67msec (1/60).

        Examples
        --------
        >>> name.getIntegrationTime()
        >>> 1.0
        """
        return self.keithley.get('GetNPLC')

    def setIntegrationTime(self, nplc):
        """
        Set the number of integration rate.

        Parameters
        ----------
        Value: Float, i.e.: 0.01 to 10 (PLCs). Where 1 PLC for 60Hz is 16.67msec (1/60).

        Examples
        --------
        >>> name.setIntegrationTime(0.01)
        """

        if (nplc < 0.01 or nplc > 10):
            raise ValueError('Invalid number - It should be 0.01 to 10')
        self.keithley.put('SetNPLC', nplc)

    def getAverageTControl(self):
        """
        Get the filter control.
        Default: REP.

        Returns
        -------
        Value: String, i.e.: REP or MOV.

        Examples
        --------
        >>> name.getAverageTControl()
        >>> 'REP'
        """

        return self.keithley.get('GetAverTCon')

    def setAverageTControl(self, tcon):
        """
        Set the filter control.

        Parameters
        ----------
        Value: String, i.e.: 'REP' or 'MOV', where REP means 'Repeat' and MOV means 'Moving'.

        Examples
        --------
        >>> name.setAverageTControl('MOV')
        """

        if (tcon != 'REP' and tcon != 'MOV'):
            raise ValueError('Invalid name - It should be REP or MOV')
        self.keithley.put('SetAverTCon', bytes(tcon, 'ascii'))

    def getZeroCheck(self):
        """
        Get the status of Zero Check (enable/disable).
        Default: disable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.getZeroCheck()
        >>> False
        """

        return bool(self.keithley.get('GetZeroCheck'))

    def setZeroCheck(self, check):
        """
        Set enable/disable for Zero Check.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Returns
        -------
        One value (1).

        Examples
        --------
        >>> name.setZeroCheck(1)
        >>> 1
        """

        if (check != 0 and check != 1):
            raise ValueError('Invalid number - It should be 0 or 1')
        return self.keithley.put('SetZeroCheck', check)

    def getZeroCorrect(self):
        """
        Get the status of Zero Correct (enable/disable).
        Default: disable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).
    
        Examples
        --------
        >>> name.getZeroCorrect()
        >>> False
        """

        return bool(self.keithley.get('GetZeroCor'))

    def setZeroCorrect(self, cor):
        """
        Set enable/disable for Zero Correct.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Returns
        -------
        One value (1).

        Examples
        --------
        >>> name.setZeroCorrect(1)
        >>> 1
        """

        if (cor != 0 and cor != 1):
            raise ValueError('Invalid number - It should be 0 or 1')
        return self.keithley.put('SetZeroCor', cor)

    def getAutoCurrentRange(self):
        """
        Get the status of Auto Current Range (enable/disable).
        Default: enable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.getAutoCurrentRange()
        >>> True
        """

        return bool(self.keithley.get('GetAutoCurrRange'))

    def setAutoCurrentRange(self, autorange):
        """
        Set enable/disable for Auto Current Range.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Returns
        -------
        One value (1).

        Examples
        --------
        >>> name.setAutoCurrentRange(1)
        >>> 1
        """

        if (autorange != 0 and autorange != 1):
            raise ValueError('Invalid number - It should be 0 or 1')
        return self.keithley.put('SetAutoCurrRange', autorange)

    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('GetCurrRange')

    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('SetCurrRange', curange)

    def getValue(self, **kwargs):
        """
        Get the current value of a countable device.

        Parameters
        ----------
        kwargs : value
            Where needed informations can be passed, e.g. select which channel must be read.

        Returns
        -------
        out : value
            Returns the current value of the device. Type of the value depends on device settings.
        """
        return self.getTriggerReading()

    def setCountTime(self, time):
        """
        Method to set the count time of a Keithley device.

        .. note::
            Whenever the median filter is active, changing the count time results in
            the filter being reset, so the first measurement will take additional
            time to collect new data for the filter. The extra time is proportional
            to the median filter rank. After the first measurement, the following
            measurements will have the correct integration time.
        .. note:: Unlike scalers, the count time is only an approximation. The
            requested integration time will be split into multiple parts, which includes
            the analog integration time (varying between approximatelly 10ms to 50ms),
            the digital integration time (that averages a set of 2 to 100 analog
            integrations), and other operations unrelated to integration, like auto zero
            calibration (triples the integration time) and calculation times. When
            calling this method, the digital average filter will be activated if it's
            not already and the filter type will be set to repeat.

        See also: :meth:`setIntegrationTime`, :meth:`setAverageCount`

        Parameters
        ----------
        time : value
            The target count time to be set. The allowed time range is 100ms to 15s
            (limited by software).

        Returns
        -------
        out : None
        """
        if (not self.timeBased):
            return

        if time < 0.1 or time > 15:
            raise ValueError('Invalid integration time: %g' % time)

        # Keithley timing model:
        # ----------------------
        #
        # Keithley fundamental delay is the analog integration delay, which is the
        # time it takes to measure the current using the measurement hardware.
        # The integration delay can be configured to values in the range between
        # 166,7µs to 166,7ms (or 200µs to 200ms in 50Hz electricity grids).
        # On top of the analog integration delay, two delays are significant:
        # the digital filter delay, which works as a digital integrator (when configured
        # in "repeating" mode) and the auto zero setting, which performs continuous
        # device recalibration. The digital filter works by taking multiple analog
        # measurements, then averaging the result, so it multiplies the delay by the
        # repeat count. The auto zero setting always triples the integration time.
        # So, the basic initial timing model for the Keithley device is the following:
        #
        #       (1) time = Azero*Rcount*Atime
        #
        # Where time is the final count time, Azero is 2,97 when auto zero is enabled,
        # or 0,95 when auto zero is disabled, Rcount is the digital average repeat count and
        # Atime is the analog integration time. For example, with auto zero enabled,
        # integration time of 33,33ms and a repeat count of 10,0, the total count time is
        # 990ms.
        #
        # Empirical tests with equation (1) were done by trying analog integration times
        # around 33,33ms and choosing a suitable repeat count. The region around 33,33ms
        # was chosen by chance and because it's inside the recommended integration range
        # (16,7ms to 166,7ms).
        #
        # The calculated repeat count is rounded to an integer and the analog integration
        # time is corrected back. For example, given an integration time of 0,5 seconds,
        # we set 33,67ms (close to 33,33ms) and repeat count 5, as calculated below:
        #
        #       time = Azero*Rcount*Atime
        #       0,5 = 2,97*Rcount*0,03333
        #       Rcount = 5,0505...
        #       Rcount,rounded = 5
        #       time = Azero*Rcount,rounded*Atime
        #       0,5 = 2,97*5*Atime
        #       Atime = 33,67ms
        #
        # Using the above procedure to compare the modeled time and the real Keithley time
        # resulted in a line with factor and displacement errors:
        #
        #       (2) time = Azero*Rcount*Atime*f + d
        #
        # The variable f represents an error proportional to the integration time,
        # while d represents a fixed delay. For example, f could be due to hardware
        # delays while integrating and computation overhead to calculate the
        # digital average. The delay d is due to "warm up" and "clean up" procedures,
        # in particular, due to device to computer communication. With a serial port
        # configuration, the factors were found to be the following:
        #
        #       (2') time = Azero*Rcount*Atime*f' + d'
        #       (3) f' = 1,09256, d' = 0,0427154
        #
        # The serial port communication is significant and happens before and after the
        # acquisition. It takes 20,83ms for sending and receiving a measurement using the
        # serial port (20 bytes/960 bytes/s). This value needs to be removed from the
        # measured delay, since it's not part of the integration time. The resulting
        # equation is accurate enough for usage as the timing model for Keithley:
        #
        #       (4) time = Azero*Rcount*Atime*1,09256 + 0,021882
        #
        # Equation (4) can then be reversed and solved for Rcount and Atime:
        #
        #       (5) Rcount = (time-0.021882)/(Azero*Atime*1,09256)
        #       (6) Atime = (time-0.021882)/(Rcount*Atime*1,09256)
        #
        # The algorithm implemented calculates (5) assuming initially Atime == 33,33ms,
        # then rounds Rcount to an integer, calculates (6) with the resulting Rcount and
        # if results are not within proper bounds, it iterates a second time.
        #
        # Note: it is known that the first and second measurements after changing the
        # count time takes longer than usual to return a measurement. The timing becomes
        # more precise starting from the third measurement. When the median filter is
        # active, the first measurement takes much longer, because the median filter
        # buffer is cleaned and filled again.

        azero = 2.97 if self.getAutoZeroing() else 0.95
        f = 1.09256
        d = 0.021882

        # Analog integration time initially equal to 33,33ms
        atime = 2 / 60

        # Repeat count must be between 2 and 100
        rcount = int((time - d) / (azero * atime * f))
        rcount = max(rcount, 2)
        rcount = min(rcount, 100)

        # Then, solve for integration time
        atime = (time - d) / (azero * rcount * f)

        # If integration time is out of range, fix it and iterate one more time
        if atime < 0.1 / 60 or atime > 10 / 60:
            atime = max(atime, 0.1 / 60)
            atime = min(atime, 10 / 60)
            rcount = int((time - d) / (azero * atime * f))
            rcount = max(rcount, 2)
            rcount = min(rcount, 100)
            atime = (time - d) / (azero * rcount * f)
            atime = max(atime, 0.1 / 60)
            atime = min(atime, 10 / 60)

        changed = False

        # Integration time must be rounded to 2 digits or Keithley will crash
        nplc = round(atime * 60, 2)
        if nplc != self.getIntegrationTime():
            self.setIntegrationTime(nplc)
            changed = True

        if rcount != self.getAverageCount():
            self.setAverageCount(rcount)
            changed = True

        if not self.getAverageDigitalFilter():
            self.setAverageDigitalFilter(1)
            changed = True

        if self.getAverageTControl() != 'REP':
            self.setAverageTControl('REP')
            changed = True

        if changed:
            ca.poll(0.05)

    def setPresetValue(self, channel, val):
        """
        Abstract method to set the preset count of a countable target device.

        Parameters
        ----------
        channel : `int`
            The monitor channel number
        val : `int`
            The preset value

        Returns
        -------
        out : None
        """
        pass

    def startCount(self):
        """
        Abstract method trigger a count in a counter

        """
        if (not self.getStatusContinuesMode()):
            self._counting = True
            self.keithley.put("OneMeasure", 1)
        pass

    def stopCount(self):
        """
        Abstract method stop a count in a counter

        """
        if (not self.getStatusContinuesMode()):
            self.keithley.put("OneMeasure", 0)
        pass

    def canMonitor(self):
        """
        Abstract method to check if the device can or cannot be used as monitor.

        Returns
        -------
        out : `bool`
        """
        return False

    def canStopCount(self):
        """
        Abstract method to check if the device can or cannot stop the count and return values.

        Returns
        -------
        out : `bool`
        """
        return True
Пример #14
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)
Пример #15
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)
Пример #16
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()
Пример #17
0
class Keithley6514(StandardDevice, ICountable):
    """

    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 onStatusChange(self, value, **kw):
        self._counting = (value == 1)
    
    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)

    def isCountingPV(self):
        return (self.keithley.get('CNT') == 1)

    def isCounting(self):
        return self._counting

    def wait(self):
        while(self.isCounting()):
            ca.poll(0.00001)

    def getTriggerReading(self):            
        """
        Trigger and return reading(s).

        Returns
        -------
        Value: Float, e.g.: -6.0173430000000003e-16.

        Examples
        --------
        >>> name.getTriggerReading()
        >>> -1.0221850000000001e-15
        """
        return self.pvMeasure.get(use_monitor=False)
        #return self.keithley.get('Measure')
    
    def getCountNumberReading(self):            
        """
        Count the number of reading(s).

        Returns
        -------
        Value: Integer, e.g.: 963.

        Examples
        --------
        >>> name.CountNumberReading()
        >>> 161.0
        """

        return self.keithley.get('Count')

    def getStatusContinuesMode(self):            
        """
        Get the status of Continues Mode (enable/disable).
        Default: enable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.getStatusContinuesMode()
        >>> True
        """

        return bool(self.keithley.get('ContinuesMode'))

    def setStatusContinuesMode(self, cmode):
        """
        Set enable/disable to continues mode. Let this enable if you want a continuing measuring.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.setStatusContinuesMode(0)
        """

        if(cmode != 0 and cmode != 1):
            raise ValueError('Invalid number - It should be 0 or 1') 
        self.keithley.put('ContinuesMode', cmode)

    def getAutoZeroing(self):
        """
        Get the status of Auto Zero (enable/disable).
        Default: enable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.getAutoZeroing()
        >>> True
        """

        return bool(self.keithley.get('GetAutoZero'))

    def setAutoZeroing(self, autozero):    
        """
        Set enable/disable for Auto Zero.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.setAutoZeroing(1)
        """

        if(autozero != 0 and autozero != 1):
            raise ValueError('Invalid number - It should be 0 or 1') 
        self.keithley.put('SetAutoZero', autozero)

    def getMedianFilter(self):
        """
        Get the status of Median Filter (enable/disable).
        Default: enable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.getMedianFilter()
        >>> True
        """

        return bool(self.keithley.get('GetMed'))

    def setMedianFilter(self, med):
        """
        Set enable/disable for Median Filter.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.setMedianFilter(1)
        """

        if(med != 0 and med != 1):
            raise ValueError('Invalid number - It should be 0 or 1') 
        self.keithley.put('SetMed', med)

    def getMedianRank(self):
        """
        Get the value of Median Rank, this number of sample readings are between 1 to 5.
        Default: 5.

        Returns
        -------
        Value: Integer, i.e.: 1 to 5.

        Examples
        --------
        >>> name.getMedianRank()
        >>> 5.0
        """

        return self.keithley.get('GetMedRank')

    def setMedianRank(self, medrank): 
        """
        Set the number of sample readings used for the median calculation.

        Parameters
        ----------
        Value: Integer, i.e.: 1 to 5.

        Examples
        --------
        >>> name.setMedianRank(3)
        """

        if (medrank < 1 or medrank > 5):
            raise ValueError('Invalid number - It should be 1 to 5') 
        self.keithley.put('SetMedRank', medrank)

    def getAverageDigitalFilter(self):
        """
        Get the status of Digital Filter (enable/disable).
        Default: enable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.getAverageDigitalFilter()
        >>> True
        """

        return bool(self.keithley.get('GetAver'))

    def setAverageDigitalFilter(self, aver):
        """
        Set enable/disable for Digital Filter.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.setAverageDigitalFilter(1)
        """

        if(aver != 0 and aver != 1):
            raise ValueError('Invalid number - It should be 0 or 1') 
        self.keithley.put('SetAver', aver)

    def getAverageCount(self): 
        """
        Get the number of filter count.
        Default: 10.

        Returns
        -------
        Value: Integer, i.e.: 2 to 100.

        Examples
        --------
        >>> name.getAverageCount()
        >>> 10.0
        """

        return self.keithley.get('GetAverCoun')

    def setAverageCount(self, avercoun):
        """
        Set the number of filter count.

        Parameters
        ----------
        Value: Integer, i.e.: 2 to 100.

        Examples
        --------
        >>> name.setAverageCount(80)
        """

        if (avercoun < 2 or avercoun > 100):
            raise ValueError('Invalid number - It should be 2 to 100') 
        self.keithley.put('SetAverCoun', avercoun)

    def getIntegrationTime(self):
        """
        Get the number of integration rate.
        Default: 1.

        Returns
        -------
        Value: Float, i.e.: 0.01 to 10 (PLCs). Where 1 PLC for 60Hz is 16.67msec (1/60).

        Examples
        --------
        >>> name.getIntegrationTime()
        >>> 1.0
        """
        return self.keithley.get('GetNPLC')

    def setIntegrationTime(self, nplc):
        """
        Set the number of integration rate.

        Parameters
        ----------
        Value: Float, i.e.: 0.01 to 10 (PLCs). Where 1 PLC for 60Hz is 16.67msec (1/60).

        Examples
        --------
        >>> name.setIntegrationTime(0.01)
        """

        if (nplc < 0.01 or nplc > 10):
            raise ValueError('Invalid number - It should be 0.01 to 10') 
        self.keithley.put('SetNPLC', nplc)

    def getAverageTControl(self):
        """
        Get the filter control.
        Default: REP.

        Returns
        -------
        Value: String, i.e.: REP or MOV.

        Examples
        --------
        >>> name.getAverageTControl()
        >>> 'REP'
        """

        return self.keithley.get('GetAverTCon')

    def setAverageTControl(self, tcon):
        """
        Set the filter control.

        Parameters
        ----------
        Value: String, i.e.: 'REP' or 'MOV', where REP means 'Repeat' and MOV means 'Moving'.

        Examples
        --------
        >>> name.setAverageTControl('MOV')
        """

        if (tcon != 'REP' and tcon != 'MOV'):
            raise ValueError('Invalid name - It should be REP or MOV') 
        self.keithley.put('SetAverTCon', bytes(tcon, 'ascii'))

    def getZeroCheck(self):
        """
        Get the status of Zero Check (enable/disable).
        Default: disable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.getZeroCheck()
        >>> False
        """

        return bool(self.keithley.get('GetZeroCheck'))

    def setZeroCheck(self, check):
        """
        Set enable/disable for Zero Check.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Returns
        -------
        One value (1).

        Examples
        --------
        >>> name.setZeroCheck(1)
        >>> 1
        """

        if(check != 0 and check != 1):
            raise ValueError('Invalid number - It should be 0 or 1') 
        return self.keithley.put('SetZeroCheck', check)

    def getZeroCorrect(self):
        """
        Get the status of Zero Correct (enable/disable).
        Default: disable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).
    
        Examples
        --------
        >>> name.getZeroCorrect()
        >>> False
        """

        return bool(self.keithley.get('GetZeroCor'))

    def setZeroCorrect(self, cor):
        """
        Set enable/disable for Zero Correct.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Returns
        -------
        One value (1).

        Examples
        --------
        >>> name.setZeroCorrect(1)
        >>> 1
        """

        if(cor != 0 and cor != 1):
            raise ValueError('Invalid number - It should be 0 or 1') 
        return self.keithley.put('SetZeroCor', cor)

    def getAutoCurrentRange(self):
        """
        Get the status of Auto Current Range (enable/disable).
        Default: enable.

        Returns
        -------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Examples
        --------
        >>> name.getAutoCurrentRange()
        >>> True
        """

        return bool(self.keithley.get('GetAutoCurrRange'))

    def setAutoCurrentRange(self, autorange):
        """
        Set enable/disable for Auto Current Range.

        Parameters
        ----------
        Value: Boolean, i.e.: 0 - False (Off/Disable), 1 - True (On/Enable).

        Returns
        -------
        One value (1).

        Examples
        --------
        >>> name.setAutoCurrentRange(1)
        >>> 1
        """

        if(autorange != 0 and autorange != 1):
            raise ValueError('Invalid number - It should be 0 or 1') 
        return self.keithley.put('SetAutoCurrRange', autorange)

    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('GetCurrRange')

    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('SetCurrRange', curange)

    def getValue(self, **kwargs):
        """
        Get the current value of a countable device.

        Parameters
        ----------
        kwargs : value
            Where needed informations can be passed, e.g. select which channel must be read.

        Returns
        -------
        out : value
            Returns the current value of the device. Type of the value depends on device settings.
        """
        return self.getTriggerReading()

    def setCountTime(self, time):
        """
        Method to set the count time of a Keithley device.

        .. note::
            Whenever the median filter is active, changing the count time results in
            the filter being reset, so the first measurement will take additional
            time to collect new data for the filter. The extra time is proportional
            to the median filter rank. After the first measurement, the following
            measurements will have the correct integration time.
        .. note:: Unlike scalers, the count time is only an approximation. The
            requested integration time will be split into multiple parts, which includes
            the analog integration time (varying between approximatelly 10ms to 50ms),
            the digital integration time (that averages a set of 2 to 100 analog
            integrations), and other operations unrelated to integration, like auto zero
            calibration (triples the integration time) and calculation times. When
            calling this method, the digital average filter will be activated if it's
            not already and the filter type will be set to repeat.

        See also: :meth:`setIntegrationTime`, :meth:`setAverageCount`

        Parameters
        ----------
        time : value
            The target count time to be set. The allowed time range is 100ms to 15s
            (limited by software).

        Returns
        -------
        out : None
        """
        if(not self.timeBased):
            return
        
        if time < 0.1 or time > 15:
            raise ValueError('Invalid integration time: %g' % time)

        # Keithley timing model:
        # ----------------------
        #
        # Keithley fundamental delay is the analog integration delay, which is the
        # time it takes to measure the current using the measurement hardware.
        # The integration delay can be configured to values in the range between
        # 166,7µs to 166,7ms (or 200µs to 200ms in 50Hz electricity grids).
        # On top of the analog integration delay, two delays are significant:
        # the digital filter delay, which works as a digital integrator (when configured
        # in "repeating" mode) and the auto zero setting, which performs continuous
        # device recalibration. The digital filter works by taking multiple analog
        # measurements, then averaging the result, so it multiplies the delay by the
        # repeat count. The auto zero setting always triples the integration time.
        # So, the basic initial timing model for the Keithley device is the following:
        #
        #       (1) time = Azero*Rcount*Atime
        #
        # Where time is the final count time, Azero is 2,97 when auto zero is enabled,
        # or 0,95 when auto zero is disabled, Rcount is the digital average repeat count and
        # Atime is the analog integration time. For example, with auto zero enabled,
        # integration time of 33,33ms and a repeat count of 10,0, the total count time is
        # 990ms.
        #
        # Empirical tests with equation (1) were done by trying analog integration times
        # around 33,33ms and choosing a suitable repeat count. The region around 33,33ms
        # was chosen by chance and because it's inside the recommended integration range
        # (16,7ms to 166,7ms).
        #
        # The calculated repeat count is rounded to an integer and the analog integration
        # time is corrected back. For example, given an integration time of 0,5 seconds,
        # we set 33,67ms (close to 33,33ms) and repeat count 5, as calculated below:
        #
        #       time = Azero*Rcount*Atime
        #       0,5 = 2,97*Rcount*0,03333
        #       Rcount = 5,0505...
        #       Rcount,rounded = 5
        #       time = Azero*Rcount,rounded*Atime
        #       0,5 = 2,97*5*Atime
        #       Atime = 33,67ms
        #
        # Using the above procedure to compare the modeled time and the real Keithley time
        # resulted in a line with factor and displacement errors:
        #
        #       (2) time = Azero*Rcount*Atime*f + d
        #
        # The variable f represents an error proportional to the integration time,
        # while d represents a fixed delay. For example, f could be due to hardware
        # delays while integrating and computation overhead to calculate the
        # digital average. The delay d is due to "warm up" and "clean up" procedures,
        # in particular, due to device to computer communication. With a serial port
        # configuration, the factors were found to be the following:
        #
        #       (2') time = Azero*Rcount*Atime*f' + d'
        #       (3) f' = 1,09256, d' = 0,0427154
        #
        # The serial port communication is significant and happens before and after the
        # acquisition. It takes 20,83ms for sending and receiving a measurement using the
        # serial port (20 bytes/960 bytes/s). This value needs to be removed from the
        # measured delay, since it's not part of the integration time. The resulting
        # equation is accurate enough for usage as the timing model for Keithley:
        #
        #       (4) time = Azero*Rcount*Atime*1,09256 + 0,021882
        #
        # Equation (4) can then be reversed and solved for Rcount and Atime:
        #
        #       (5) Rcount = (time-0.021882)/(Azero*Atime*1,09256)
        #       (6) Atime = (time-0.021882)/(Rcount*Atime*1,09256)
        #
        # The algorithm implemented calculates (5) assuming initially Atime == 33,33ms,
        # then rounds Rcount to an integer, calculates (6) with the resulting Rcount and
        # if results are not within proper bounds, it iterates a second time.
        #
        # Note: it is known that the first and second measurements after changing the
        # count time takes longer than usual to return a measurement. The timing becomes
        # more precise starting from the third measurement. When the median filter is
        # active, the first measurement takes much longer, because the median filter
        # buffer is cleaned and filled again.

        azero = 2.97 if self.getAutoZeroing() else 0.95
        f = 1.09256
        d = 0.021882

        # Analog integration time initially equal to 33,33ms
        atime = 2/60

        # Repeat count must be between 2 and 100
        rcount = int((time-d)/(azero*atime*f))
        rcount = max(rcount, 2)
        rcount = min(rcount, 100)

        # Then, solve for integration time
        atime = (time-d)/(azero*rcount*f)

        # If integration time is out of range, fix it and iterate one more time
        if atime < 0.1/60 or atime > 10/60:
            atime = max(atime, 0.1/60)
            atime = min(atime, 10/60)
            rcount = int((time-d)/(azero*atime*f))
            rcount = max(rcount, 2)
            rcount = min(rcount, 100)
            atime = (time-d)/(azero*rcount*f)
            atime = max(atime, 0.1/60)
            atime = min(atime, 10/60)

        changed = False
        
        # Integration time must be rounded to 2 digits or Keithley will crash
        nplc = round(atime*60, 2)
        if nplc != self.getIntegrationTime():
            self.setIntegrationTime(nplc)
            changed = True

        if rcount != self.getAverageCount():
            self.setAverageCount(rcount)
            changed = True

        if not self.getAverageDigitalFilter():
            self.setAverageDigitalFilter(1)
            changed = True

        if self.getAverageTControl() != 'REP':
            self.setAverageTControl('REP')
            changed = True

        if changed:
            ca.poll(0.05)
            
    def setPresetValue(self, channel, val):
        """
        Abstract method to set the preset count of a countable target device.

        Parameters
        ----------
        channel : `int`
            The monitor channel number
        val : `int`
            The preset value

        Returns
        -------
        out : None
        """
        pass

    def startCount(self):
        """
        Abstract method trigger a count in a counter

        """
        if(not self.getStatusContinuesMode()):
            self._counting = True
            self.keithley.put("OneMeasure", 1)        
        pass
    
    def stopCount(self):
        """
        Abstract method stop a count in a counter

        """
        if(not self.getStatusContinuesMode()):
            self.keithley.put("OneMeasure", 0)        
        pass

    def canMonitor(self):
        """
        Abstract method to check if the device can or cannot be used as monitor.

        Returns
        -------
        out : `bool`
        """        
        return False

    def canStopCount(self):
        """
        Abstract method to check if the device can or cannot stop the count and return values.

        Returns
        -------
        out : `bool`
        """        
        return True