Пример #1
0
class BeamElement(HasTimeout, Moveable):
    """
    Class for readout of the MIRA shutter via digital input card, and closing
    the shutter via digital output (tied into Pilz security system).
    """

    valuetype = oneof('in', 'out')

    attached_devices = {
        'valve': Attach('in/out pressure valve', Moveable),
        'switch_in': Attach('limit switch for "in" position', Readable),
        'switch_out': Attach('limit switch for "out" position', Readable),
    }

    parameter_overrides = {
        'timeout': Override(default=10),
        'unit': Override(mandatory=False, default=''),
    }

    def doStatus(self, maxage=0):
        is_in = self._attached_switch_in.read(maxage)
        is_out = self._attached_switch_out.read(maxage)
        valvepos = self._attached_valve.read(maxage)
        if (is_in and valvepos == 'in') or (is_out and valvepos == 'out'):
            return status.OK, 'idle'
        return status.BUSY, 'moving'

    def doRead(self, maxage=0):
        return self._attached_valve.read(maxage)

    def doStart(self, target):
        self._attached_valve.start(target)

    def doReset(self):
        multiReset(self._adevs)
Пример #2
0
class Detector(Moveable):
    """Combination device for the detector axes."""

    valuetype = tupleof(float, float, float)

    attached_devices = {
        'x': Attach('X motor', Moveable),
        'y': Attach('Y motor', Moveable),
        'z': Attach('Z motor', Moveable),
    }

    parameter_overrides = {
        'unit':   Override(mandatory=False, default='mm'),
        'fmtstr': Override(default='%.1f, %.2f, %.0f'),
    }

    def doRead(self, maxage=0):
        return (self._attached_x.read(maxage),
                self._attached_y.read(maxage),
                self._attached_z.read(maxage))

    def doIsAllowed(self, pos):
        for (i, name, dev) in [(0, 'x', self._attached_x),
                               (1, 'y', self._attached_y),
                               (2, 'z', self._attached_z)]:
            ok, why = dev.isAllowed(pos[i])
            if not ok:
                return False, name + ': ' + why
        return True, ''

    def doStart(self, pos):
        self._attached_x.start(pos[0])
        self._attached_y.start(pos[1])
        self._attached_z.start(pos[2])
Пример #3
0
class DimetixLaser(CanDisable, HasOffset, Readable):

    attached_devices = {
        'signal': Attach('signal strength device', Readable),
        'value': Attach('value device', Readable),
    }

    parameter_overrides = {
        'unit': Override(volatile=True, mandatory=False),
    }

    parameters = {
        'signallimit':
        Param('minimal signal strength for valid reading',
              type=floatrange(0.),
              default=8000),
        'invalidvalue':
        Param('value to indicate invalid value',
              type=intrange(-2000, -2000),
              default=-2000),
    }

    def doRead(self, maxage=0):
        if self._attached_signal.read(maxage) < self.signallimit:
            return self.invalidvalue
        return self._attached_value.read(maxage)

    def doReadUnit(self):
        return self._attached_value.unit

    def doPoll(self, n, maxage):
        self._attached_signal.poll(n, maxage)
        self._attached_signal.poll(n, maxage)
Пример #4
0
class CCRSwitch(Moveable):
    """Class for FRM II sample environment CCR box switches (gas/vacuum).
    """

    attached_devices = {
        'write': Attach('TACO digital output device', DigitalOutput),
        'feedback': Attach('TACO digital input device (feedback)',
                           DigitalInput),
    }

    parameter_overrides = {
        'fmtstr': Override(default='%d'),
        'unit': Override(default='', mandatory=False),
    }

    valuetype = oneofdict({'on': 1, 'off': 0})

    def doStart(self, target):
        if self.read(0) != target:
            self._attached_write.start(1)

    def doStatus(self, maxage=0):
        return status.OK, ''

    def doRead(self, maxage=0):
        return self._attached_feedback.read(maxage)
Пример #5
0
class SampleIllumination(Readable):

    parameters = {
        's1pos':
        Param('Slit 1 position relative to sample', mandatory=True,
              type=float),
        's2pos':
        Param('Slit 2 position relative to sample', mandatory=True,
              type=float),
        'f0':
        Param('Footprint for n * pi, 0 <= n <= N', type=float, default=nan)
    }
    attached_devices = {
        's1': Attach('First slit', Slit),
        's2': Attach('Second slit', Slit),
        'theta': Attach('Incidence angle', Readable)
    }

    def doRead(self, maxage=0):
        """Calculate the beam footprint (illumination on the sample)."""
        # l1= distance between slits
        l1 = self.s1pos - self.s2pos
        # l2 distance from last slit to sample
        l2 = self.s2pos
        # get slit width
        s1w = self._attached_s1.width.read(maxage)
        s2w = self._attached_s2.width.read(maxage)
        theta = self._attached_theta.read(maxage)

        denominator = abs(sin(radians(theta)))
        if denominator < 1e-4:
            return self.f0
        return (min(s1w, s2w) + l2 / l1 * (s1w + s2w)) / denominator
Пример #6
0
class PropertyChanger(Moveable):
    """This is essentially a ParamDevice

    and can be replace once Controller uses single setters
    (NICOS-style interface).
    """

    attached_devices = {
        'chopper': Attach('Chopper controller', BaseChopperController),
        'chdelay': Attach('Setting chopper delay', Moveable),
    }

    def doStatus(self, maxage=0):
        stat = self._attached_chopper.status(maxage)
        if stat[0] != status.OK:
            return stat[0], 'changing'
        return status.OK, 'idle'

    def doRead(self, maxage=0):
        return getattr(self._attached_chopper, self._prop)

    def doStart(self, target):
        ch5_90deg_offset = self._attached_chopper.ch5_90deg_offset
        chwl, chspeed, chratio, chst = self._chopper_params(target)
        _chdelay = calc.calculateChopperDelay(chwl, chspeed, chratio, chst,
                                              ch5_90deg_offset)
        self.log.debug('setting chopper delay to: %d', _chdelay)
        self._attached_chdelay.move(_chdelay)
        self._attached_chopper._change(self._prop, target)

    def doReadTarget(self):
        return getattr(self._attached_chopper, self._prop)
Пример #7
0
class TestController(IsController, Moveable):

    attached_devices = {
        'dev1': Attach('First device', Moveable),
        'dev2': Attach('Second device', Moveable),
    }

    def isAdevTargetAllowed(self, adev, adevtarget):
        if adev == self._attached_dev1:
            other = self._attached_dev2.read()
            if other < adevtarget:
                return (False, 'dev1 can only move to values smaller'
                        ' than %r' % other)
        if adev == self._attached_dev2:
            other = self._attached_dev1.read()
            if other > adevtarget:
                return (False, 'dev2 can only move to values greater'
                        ' than %r' % other)
        return (True, 'Allowed')

    def doRead(self, maxage=0):
        return self._value

    def doIsAllowed(self, target):
        if target[0] > target[1]:
            return (False, 'dev1 can only move to values greater' ' than dev2')
        return (True, 'Allowed')

    def doStart(self, target):
        self._value = target
        self._attached_dev1.start(target[0])
        self._attached_dev2.start(target[1])
Пример #8
0
class LiftingSXTal(SXTalBase):

    attached_devices = {
        'gamma': Attach('gamma device', Moveable),
        'omega': Attach('omega device', Moveable),
        'nu': Attach('nu device (counter lifting axis)', Moveable),
    }

    def _extractPos(self, pos):
        return [
            ('gamma', np.rad2deg(pos.gamma)),
            ('omega', np.rad2deg(pos.omega)),
            ('nu', np.rad2deg(pos.nu)),
        ]

    def _convertPos(self, pos, wavelength=None):
        return pos.asL(wavelength)

    def _readPos(self, maxage=0):
        return PositionFactory('l',
                               gamma=self._attached_gamma.read(maxage),
                               omega=self._attached_omega.read(maxage),
                               nu=self._attached_nu.read(maxage))

    def _createPos(self, gamma, omega, nu):
        return PositionFactory('l', gamma=gamma, omega=omega, nu=nu)

    def getScanWidthFor(self, hkl):
        """Get scanwidth.

        TODO: make this dependent on angles.
        """
        return 5.0
Пример #9
0
class NiagExpShutter(NiagShutter):
    """ the "NiagExpShutter add the shutter speed control
    functionality to the NiagShutter base class, implemented
    as an additional parameter called 'fast'

    It uses 3 additional digital IOs:
    - 2 outputs to switch to the slow and fast modes, respectively
    - 1 input to check which mode is active (slow = 0, fast = 1) """

    attached_devices = {
        'do_fast': Attach('Output to set the shutter speed to fast', Moveable),
        'do_slow': Attach('Output to set the shutter speed to slow', Moveable),
        'is_fast': Attach('Input to check if the shutter speed is fast',
                          Readable),
    }

    parameters = {
        'fast':
        Param('Fast shutter opening/closing',
              mandatory=True,
              default=False,
              settable=True,
              type=bool,
              volatile=True),
    }

    def doReadFast(self):
        return self._attached_is_fast.read(0) == 0

    # depending on the value, send the pulse to the corresponding output
    def doWriteFast(self, value):
        if value:
            self._attached_do_fast.move(1)
        else:
            self._attached_do_slow.move(1)
Пример #10
0
class McStasImage(BaseImage):

    parameter_overrides = {
        'size': Override(default=(625, 450)),
        'mcstasprog': Override(default='biodiff_fast'),
        'mcstasfile': Override(default='PSD_BIODIFF_total.psd'),
        'writedelay': Override(default=0.3),
    }

    attached_devices = {
        'sample': Attach('Mirror sample', SXTalSample),
        's1': Attach('Slit 1', Readable),
        's2': Attach('Slit 2', Readable),
        'omega': Attach('Sample omega rotation', Readable),
        'wavelength': Attach('Incoming wavelength', Readable),
        # 'sample_x': Attach('Sample position x', Readable),
        # 'sample_y': Attach('Sample position y', Readable),
        # 'sample_z': Attach('Sample position z', Readable),
        # 'beamstop': Attach('Beam stop positon', Readable),
    }

    def _prepare_params(self):
        params = []
        params.append('SLIT_S1=%s' % self._attached_s1.read(0))
        params.append('SLIT_S2=%s' % self._attached_s2.read(0))
        params.append('omega=%s' % self._attached_omega.read(0))
        sample = self._attached_sample
        params.append('cell_a=%s' % sample.a)
        params.append('cell_b=%s' % sample.b)
        params.append('cell_c=%s' % sample.c)
        params.append('Lam=%s' % self._attached_wavelength.read(0))
        params.append('dLam=%s' % 0.05)
        params.append('REP=%s' % 1000)
        return params
Пример #11
0
class ListmodeSink(QMesyDAQSink):
    handlerclass = ListmodeSinkHandler

    attached_devices = {
        'liveimage': Attach('device to set filename', Device, optional=True),
        'tofchannel': Attach('device to get TOF settings', Device),
    }
Пример #12
0
class BeamstopSequencer(BaseSequencer):
    """
    Base class for Beamstop handling classes at SANS
    """

    attached_devices = {
        'x': Attach('Motor for X movement', Motor),
        'y': Attach('Motor for Y movement', Motor),
    }

    parameters = {
        '_in_x': Param('IN x position', type=float, default=0.0,
                       settable=True, internal=True),
        '_in_y': Param('IN y position', type=float, default=0.0,
                       settable=True, internal=True),
    }

    def _fix(self):
        self._attached_x.fix()
        self._attached_y.fix()

    def _release(self):
        self._attached_x.release()
        self._attached_y.release()

    def _save_pos(self):
        self._in_x = self._attached_x.read(0)
        self._in_y = self._attached_y.read(0)
Пример #13
0
class VSSpeed(BaseSequencer):
    """
    This class controls the speed of the velocity selector. While the
    readback is a plain analog readable, setting the set point is
    special: The 12 bit set point is split across two individual
    bytes of a digital output port.
    """

    attached_devices = {
        'state': Attach('Device for controlling the state of the VS',
                        Moveable),
        'setp': Attach('Device for setting the velocity setpoint',
                       Moveable),
        'rbv': Attach('Readback for the speed',
                      Readable)
    }

    _target = None

    def isAllowed(self, pos):
        test, reason = self._attached_state.isAllowed('on')
        if not test:
            return test, reason
        if pos < 0 or pos > 124.35:
            return False, 'Pos outside range 0 - 124.35 Hz'
        return True, ''

    def _generateSequence(self, target):
        seq = []

        if target < 5:
            seq.append(SeqDev(self._attached_setp, 0))
            seq.append(SeqWaitValue(self._attached_rbv, 0, 5))
            seq.append(SeqDev(self._attached_state, 'off'))
        elif abs(self._attached_rbv.read(0) - target) >= 1.0:
            seq.append(SeqDev(self._attached_state, 'on'))
            seq.append(SeqDev(self._attached_setp, target))
            seq.append(SeqWaitValue(self._attached_rbv, target, 1.0))
            # Repeat enough of this pair until sufficient stabilisation
            # has been reached
            seq.append(SeqSleep(5))
            seq.append(SeqWaitValue(self._attached_rbv, target, 1.0))
            seq.append(SeqSleep(5))
            seq.append(SeqWaitValue(self._attached_rbv, target, 1.0))
            seq.append(SeqSleep(5))
            seq.append(SeqWaitValue(self._attached_rbv, target, 1.0))

        return seq

    def doRead(self, maxage=0):
        readval = self._attached_rbv.read(maxage)
        if readval > 5:
            return readval
        return 0

    def doStatus(self, maxage=0):
        if self._seq_is_running():
            return status.BUSY, 'Ramping up speed'
        return status.OK, 'Arrived'
Пример #14
0
class TASConstant(Moveable):
    """
    Common class for TAS k, E and lambda pseudo-devices.
    """

    parameters = {
        'scanmode': Param('Scanmode to set', type=oneof(*SCANMODES),
                          mandatory=True),
    }

    parameter_overrides = {
        'unit':     Override(volatile=True),
    }

    attached_devices = {
        'base': Attach('Device to move (mono or ana)', Moveable),
        'tas':  Attach('The spectrometer for setting scanmode', TAS),
    }

    valuetype = float

    hardware_access = False

    def doStatus(self, maxage=0):
        return self._attached_base.status(maxage)

    def _getWaiters(self):
        return [self._attached_base]

    def _start(self, k):
        # first drive there, to determine if it is within limits
        tas = self._attached_tas
        base = self._attached_base
        pos = from_k(k, base.unit)
        base.start(pos)
        msg = False
        if tas.scanmode != self.scanmode:
            tas.scanmode = self.scanmode
            msg = True
        if tas.scanconstant != pos:
            tas.scanconstant = pos
            msg = True
        return msg

    def doReadUnit(self):
        # needed for "does volatile param have a doRead" checking
        raise NotImplementedError

    def doStop(self):
        self._attached_base.stop()

    def fix(self, reason=''):
        # fix the base as well, avoids surprises
        Moveable.fix(self, reason)
        return self._attached_base.fix(reason)

    def release(self):
        Moveable.release(self)
        return self._attached_base.release()
Пример #15
0
class MariaDetector(Detector):

    attached_devices = {
        "shutter": Attach("Shutter to open before exposure", Moveable),
        "lives": Attach("Live channels", PassiveChannel,
                        multiple=True, optional=True)
    }

    parameters = {
        "ctrl_shutter": Param("Open shutter automatically before "
                              "exposure?", type=bool, settable=True,
                              mandatory=False, default=True),
    }

    def doStart(self):
        # open shutter before exposure
        if self.ctrl_shutter:
            self._attached_shutter.maw(OPEN)
        Detector.doStart(self)

    def doFinish(self):
        Detector.doFinish(self)
        if self.ctrl_shutter and self._attached_shutter.read() == CLOSED:
            raise InvalidValueError(self, 'shutter not open after exposure, '
                                    'check safety system')

    def _getWaiters(self):
        adevs = dict(self._adevs)
        if not self.ctrl_shutter:
            adevs.pop("shutter")
        return adevs

    def _presetiter(self):
        for i, dev in enumerate(self._attached_lives):
            if i == 0:
                yield ("live", dev)
            yield ("live%d" % (i + 1), dev)
        for itm in Detector._presetiter(self):
            yield itm

    def doSetPreset(self, **preset):
        Detector.doSetPreset(self, **preset)
        preset = self._getPreset(preset)
        if not preset:
            return

        for dev in self._attached_lives:
            dev.islive = False

        for name in preset:
            if name in self._presetkeys and self._presetkeys[name] and \
                    name.startswith("live"):
                dev = self._presetkeys[name]
                dev.ismaster = True
                dev.islive = True
        self.log.debug("   presets: %s", preset)
        self.log.debug("presetkeys: %s", self._presetkeys)
        self.log.debug("   masters: %s", self._masters)
        self.log.debug("    slaves: %s", self._slaves)
Пример #16
0
class SelectorLambda(Moveable):
    """
    Control selector wavelength directly, converting between speed and
    wavelength.

    This uses not the default conversion from the Astrium selector classes,
    since the selector is tilted against the beam, and it is easier to use
    the constant determined by wavelength calibration.

    This class allows two calibration settings, determined by the current
    value of a (manually moved) "tilt" device.
    """

    parameters = {
        'constants':
        Param(
            'Conversion constants: '
            'lam[Ang] = constant/speed[Hz] + offset',
            type=tupleof(float, float),
            mandatory=True,
            unit='Ang Hz'),
        'offsets':
        Param('Conversion offsets: '
              'lam[Ang] = constant/speed[Hz] + offset',
              type=tupleof(float, float),
              mandatory=True,
              unit='Ang'),
    }

    attached_devices = {
        'seldev': Attach('The selector speed device', Moveable),
        'tiltdev': Attach('The tilt device', Readable),
    }

    hardware_access = False

    def doRead(self, maxage=0):
        tilted = bool(self._attached_tiltdev.read(maxage))
        speed = self._attached_seldev.read(maxage)
        return (60 * self.constants[tilted] / speed) + self.offsets[tilted] \
            if speed else -1

    def doIsAllowed(self, value):
        if value == 0:
            return False, 'zero wavelength not allowed'
        tilted = bool(self._attached_tiltdev.read(0))
        speed = int(
            round(60 * self.constants[tilted] /
                  (value - self.offsets[tilted])))
        return self._attached_seldev.isAllowed(speed)

    def doStart(self, value):
        tilted = bool(self._attached_tiltdev.read(0))
        speed = int(
            round(60 * self.constants[tilted] /
                  (value - self.offsets[tilted])))
        self.log.debug('moving selector to %f rpm', speed)
        self._attached_seldev.start(speed)
Пример #17
0
class MagnetSampleTheta(Moveable):
    """Class for controlling the sample rotation inside a magnet that is built
    with significant dark angles that must be avoided for incoming and
    outgoing beam, by rotating the magnet itself on the sample table.
    """

    attached_devices = {
        'sample_theta': Attach('Sample-only theta motor', Moveable),
        'magnet_theta': Attach('Magnet-plus-sample motor', Moveable),
        'two_theta':    Attach('Scattering angle', Moveable),
    }

    parameters = {
        'blocked':     Param('Blocked angle range in the magnet. 0 is the '
                             'incoming beam direction', unit='deg',
                             type=listof(tupleof(float, float))),
        'windowstep':  Param('Steps in which to move the magnet when looking '
                             'for free windows', unit='deg', type=int,
                             default=5),
    }

    def _find_window(self, gamma, magnet):
        # find a free window for incoming and outgoing beam, which is closest
        # to the current position of the magnet
        result = []
        for pos in range(0, 360, self.windowstep):
            for (a1, a2) in self.blocked:
                # check for blocked incoming beam
                if in_range(pos, -a2, -a1):
                    break
                # check for blocked outgoing beam
                if in_range(pos, -a2 + 180 + gamma, -a1 + 180 + gamma):
                    break
            else:  # no "break"
                result.append(pos)
        self.log.debug('gamma: %.3f, magnet: %.3f', gamma, magnet)
        self.log.debug('new possible positions: %s', result)
        if not result:
            raise ComputationError(self, 'no position found for magnet with '
                                   'incoming and outgoing beam free')
        return min(result, key=lambda pos: abs(pos - 0.1))

    def doStart(self, pos):
        # get target for scattering angle
        gamma = self._attached_two_theta.target
        magnet = self._attached_magnet_theta.read(0)
        # determine nearest free window
        new_magnet = self._find_window(gamma, magnet)
        self._attached_magnet_theta.start(to_range(new_magnet))
        self._attached_sample_theta.start(to_range(pos - new_magnet))

    def _getWaiters(self):
        return [self._attached_sample_theta, self._attached_magnet_theta]

    def doRead(self, maxage=0):
        angle = self._attached_magnet_theta.read(maxage) + \
            self._attached_sample_theta.read(maxage)
        return to_range(angle)
Пример #18
0
class FPLCTrigger(HasTimeout, Moveable):
    """Trigger the FPLC flow and then wait for the sample to be
    ready inside the cell.

    Used as a sample environment device in kwscount().
    """

    valuetype = oneof('triggered', 'waiting')

    hardware_access = True

    attached_devices = {
        'output': Attach('start output to FPLC', Moveable),
        'input':  Attach('trigger input from FPLC', Readable),
    }

    parameters = {
        'started':   Param('Time when device was started',
                           internal=True, settable=True),
        'triggered': Param('Time when input was triggered after a start',
                           internal=True, settable=True),
    }

    parameter_overrides = {
        'fmtstr':    Override(default='%s'),
        'timeout':   Override(default=120),
        'unit':      Override(mandatory=False, default=''),
    }

    def doInit(self, mode):
        if mode == MASTER:
            self.triggered = self.started = 0

    def doStart(self, target):
        if target == 'triggered':
            self._attached_output.start(1)
            sleep(0.1)
            self._attached_output.start(0)
            self.started = currenttime()

    def doStatus(self, maxage=0):
        if self.started:
            if self.mode == MASTER and self._attached_input.read(maxage):
                self.started = 0
                self.triggered = currenttime()
                return status.OK, 'triggered'
            else:
                return status.BUSY, 'waiting'
        elif self.triggered:
            if self.mode == MASTER and currenttime() > self.triggered + 5:
                self.triggered = 0
            return status.OK, 'triggered'
        return status.OK, ''

    def doRead(self, maxage=0):
        if self.started:
            return 'waiting'
        return 'triggered'
Пример #19
0
class Polarizer(HasTimeout, Moveable):
    """Controls both the position of the polarizer and the spin flipper.
    """

    valuetype = oneof(*POL_SETTINGS)

    hardware_access = True

    attached_devices = {
        'output': Attach('output setter', Moveable),
        'input_in': Attach('input for limit switch "in" position', Readable),
        'input_out': Attach('input for limit switch "out" position', Readable),
        'flipper': Attach('3He flipper', Moveable),
    }

    parameter_overrides = {
        'fmtstr': Override(default='%s'),
        'timeout': Override(default=10),
        'unit': Override(mandatory=False, default=''),
    }

    parameters = {
        'values':
        Param('Possible values (for GUI)',
              internal=True,
              type=listof(str),
              default=POL_SETTINGS),
    }

    def doStatus(self, maxage=0):
        is_in = self._attached_input_in.read(maxage)
        is_out = self._attached_input_out.read(maxage)
        # check individual bits
        if is_in ^ is_out != 3:
            # inconsistent state, check switches
            if ((is_in & 2) and (is_out & 2)) or \
               ((is_in & 1) and (is_out & 1)):
                # both switches on?
                return status.ERROR, 'both switches on for element(s)'
            return status.BUSY, 'elements moving'
        # HasTimeout will check for target reached
        return self._attached_flipper.status(maxage)

    def doRead(self, maxage=0):
        is_in = self._attached_input_in.read(maxage)
        if is_in == 3:
            return self._attached_flipper.read()
        elif is_in > 0:
            return 'inconsistent'
        return 'out'

    def doStart(self, target):
        if target == 'out':
            self._attached_output.start(0)
        else:
            self._attached_output.start(3)
            self._attached_flipper.start(target)
Пример #20
0
class SelectorLambda(Moveable):
    """
    Control selector wavelength directly, converting between speed and
    wavelength.
    """

    parameters = {
        'twistangle': Param('Blade twist angle', mandatory=True, unit='deg'),
        'length': Param('Selector length', mandatory=True, unit='m'),
        'beamcenter': Param('Beam center position', mandatory=True, unit='m'),
        'maxspeed': Param('Max selector speed', mandatory=True, unit='rpm'),
    }

    attached_devices = {
        'seldev':
        Attach('The selector speed device', Moveable),
        'tiltdev':
        Attach('The tilt angle motor, if present', Moveable, optional=True),
    }

    hardware_access = False

    def _get_tilt(self, maxage):
        if self._attached_tiltdev:
            return self._attached_tiltdev.read(maxage)
        return 0

    def _constant(self, tiltang=0):
        """Calculate the inverse proportional constant between speed (in rpm)
        and wavelength (in A), depending on the tilt angle (in degrees).

        Formula adapted from Astrium NVS C++ source code.
        """
        v0 = 3955.98
        lambda0 = self.twistangle * 60 * v0 / (360 * self.length *
                                               self.maxspeed)
        A = 2 * self.beamcenter * pi / (60 * v0)
        return (tan(radians(tiltang)) + (A * self.maxspeed * lambda0)) / \
            (-A**2 * self.maxspeed * lambda0 * tan(radians(tiltang)) + A)

    def doRead(self, maxage=0):
        spd = self._attached_seldev.read(maxage)
        return self._constant(self._get_tilt(maxage)) / spd if spd else -1

    def doIsAllowed(self, value):
        if value == 0:
            return False, 'zero wavelength not allowed'
        speed = int(self._constant(self._get_tilt(0)) / value)
        allowed, why = self._attached_seldev.isAllowed(speed)
        if not allowed:
            why = 'requested %d rpm, %s' % (speed, why)
        return allowed, why

    def doStart(self, value):
        speed = int(self._constant(self._get_tilt(0)) / value)
        self.log.debug('moving selector to %d rpm', speed)
        self._attached_seldev.start(speed)
Пример #21
0
class KappaGon(IsController, Moveable):
    ''' Kappa goniometer base class'''

    attached_devices = {
        'ttheta': Attach('Two-theta device', Moveable),
        'omega': Attach('omega device', Moveable),
        'kappa': Attach('kappa device', Moveable),
        'phi': Attach('phi device', Moveable),
        'dx': Attach('detector movement device', Moveable),
    }

    def doRead(self, maxage=0):
        return PositionFactory(
            'k',
            ttheta=self._adevs['ttheta'].read(maxage),
            omega=self._adevs['omega'].read(maxage),
            kappa=self._adevs['kappa'].read(maxage),
            phi=self._adevs['phi'].read(maxage),
        )

    def doStart(self, pos):
        if isinstance(pos, PositionBase):
            target = pos.asK()
            self._adevs['ttheta'].start(target.theta * 2.)
            self._adevs['omega'].start(target.omega)
            self._adevs['kappa'].start(target.kappa)
            self._adevs['phi'].start(target.phi)
        else:
            raise ValueError(
                'incorrect arguments for start, needs to be a PositionBase object'
            )

    def isAdevTargetAllowed(self, adev, adevtarget):
        if adev == self._adevs['phi']:
            return True, 'Position allowed'  # phi can move freely
# for better visual indent
# pylint: disable=bad-indentation
        if adev == self._adevs['kappa']:
            if (-45 < self._adevs['omega'].target < 135
                    or 135 < self._adevs['omega'].target < 255):
                if -10 < adevtarget < 10:
                    return True, 'Position allowed'
                else:
                    return False, ' -10 < kappa < 10 for this omega position'

        if adev == self._adevs['omega']:
            if (self._adevs['ttheta'].target - adevtarget < 45):
                return False, 'Omega too close to two-theta'
            else:
                return True, 'Position OK'
        if adev == self._adevs['ttheta']:
            if (adevtarget - self._adevs['omega'].target < 45):
                return False, 'Omega too close to two-theta'
            else:
                return True, 'Position OK'
        return True, 'Position OK'
Пример #22
0
class CollimationGuides(HasTimeout, HasLimits, Moveable):
    """Controlling the collimation guide elements."""

    attached_devices = {
        'output': Attach('output setter', Moveable),
        'input_in': Attach('input for limit switch "in" position', Readable),
        'input_out': Attach('input for limit switch "out" position', Readable),
        'sync_bit': Attach('sync bit output', Moveable),
    }

    parameters = {
        'first': Param('first element controlled', type=int, default=2),
    }

    parameter_overrides = {
        'fmtstr': Override(default='%d'),
        'timeout': Override(default=10),
        'unit': Override(mandatory=False, default='m'),
        'abslimits': Override(mandatory=False, default=(2, 20)),
    }

    def doInit(self, mode):
        self.valuetype = intrange(self.first, 20)

    def doStatus(self, maxage=0):
        is_in = self._attached_input_in.read(maxage)
        is_out = self._attached_input_out.read(maxage)
        # check individual bits
        for i in range(20 - self.first):
            mask = 1 << i
            if is_in & mask == is_out & mask:
                # inconsistent state, check switches
                if is_in & mask:
                    # both switches on?
                    return status.ERROR, 'both switches on for element ' \
                        'at %d m' % (i + self.first)
                return status.BUSY, 'elements moving'
        # HasTimeout will check for target reached
        return status.OK, 'idle'

    def doRead(self, maxage=0):
        is_in = self._attached_input_in.read(maxage)
        # extract the lowest set bit (element)
        for i in range(20 - self.first):
            if is_in & (1 << i):
                return i + self.first
        return 20

    def doStart(self, target):
        # there are 18 bits for the collimation elements at 2..19 meters
        # bit 0 is the element at self.first, last bit is at 19m
        # move in all elements from 19m to target (20m = all open)
        bits = ((1 << (20 - target)) - 1) << (target - self.first)
        self._attached_output.start(bits)
        # without this bit, no outputs will be changed
        self._attached_sync_bit.start(1)
Пример #23
0
class Polarizer(Moveable):
    """Controls both the position of the polarizer and the spin flipper.
    """

    valuetype = oneof(*POL_SETTINGS)

    hardware_access = False

    attached_devices = {
        'switcher': Attach('polarizer in/out switch', Moveable),
        'flipper': Attach('flipper', Moveable),
    }

    parameters = {
        'values':
        Param('Possible values (for GUI)',
              internal=True,
              type=listof(str),
              default=POL_SETTINGS),
        'switchervalues':
        Param('Possible values for the switcher (out, in)',
              type=tupleof(str, str),
              default=('ng', 'pol')),
    }

    parameter_overrides = {
        'fmtstr': Override(default='%s'),
        'unit': Override(mandatory=False, default=''),
    }

    def doRead(self, maxage=0):
        switcher_pos = self._attached_switcher.read(maxage)
        flipper_pos = self._attached_flipper.read(maxage)
        if switcher_pos == 'unknown' or flipper_pos == 'unknown':
            return 'unknown'
        if switcher_pos == self.switchervalues[0]:
            return 'out'
        # Polarizer is a transmission supermirror => without flipper we get
        # the "down" polarization.
        if flipper_pos == 'on':
            return 'up'
        return 'down'

    def doStart(self, target):
        switch_pos = self._attached_switcher.read(0)
        if target == 'out':
            if switch_pos != self.switchervalues[0]:
                self._attached_switcher.start(self.switchervalues[0])
            self._attached_flipper.start('off')
        else:
            if switch_pos != self.switchervalues[1]:
                self._attached_switcher.start(self.switchervalues[1])
            if target == 'up':
                self._attached_flipper.start('on')
            elif target == 'down':
                self._attached_flipper.start('off')
Пример #24
0
class NIAGControl(ControlDetector):
    """"
    A detector control class which implements the NIAG CCD counting
    features:
    - restart counting when the countrate was to low
    """

    attached_devices = {
        'rate_monitor':
        Attach('Monitor to check rate against', Readable),
        'rate_threshold':
        Attach('Threshold defining the minimum acceptable '
               'rate', Moveable),
        'exp_ok':
        Attach('Indication of sufficient exposure', Readable),
    }
    _triggerFinished = None
    _start_time = None
    _stopped = False

    def doStart(self):
        self._start_time = time.time()
        self._stopped = False
        ControlDetector.doStart(self)

    def doStop(self):
        self._stopped = True
        ControlDetector.doStop(self)

    def _testRate(self):
        mon = self._attached_rate_monitor.read(0)
        thres = self._attached_rate_threshold.read(0)
        exp_ok = self._attached_exp_ok.read(0)
        if isinstance(mon, list):
            mon = mon[0]
        if exp_ok != 1:
            session.log.info('%s, should be > %d uA, is %f uA',
                             'Restarting because of insuffient count rate',
                             thres, mon)
            self.start()
            return False
        self._start_time = None
        return True

    def doIsCompleted(self):
        ret = ControlDetector.doIsCompleted(self)
        if ret and self._start_time and not self._stopped:
            return self._testRate()
        return ret

    def doRead(self, maxage=0):
        res = [self._attached_trigger.read(maxage)]
        for det in self._attached_slave_detectors:
            res = res.append(det.read(maxage))
        return res
Пример #25
0
class HAMEG8131(EpicsMoveable):
    """
    This class takes care of initialising the frequency generator,
    and switching it on or off
    """

    attached_devices = {
        'port': Attach('Port to talk directly to the device',
                       EpicsCommandReply),
        'freq': Attach('Flipper frequency',
                       EpicsMoveable),
        'amp': Attach('Flipper amplitude',
                      EpicsMoveable),
    }

    valuetype = oneof('on', 'off')

    def doInit(self, mode):
        if mode == SIMULATION:
            return
        inicommands = ['RM1', 'RFI', 'OT0', 'SW0', 'SK0',
                       'CTM', 'VPP', 'AM0', 'SIN', 'OFS:0E+0',
                       'FRQ:2.534E+5', 'AMP:2.6E+0']
        for com in inicommands:
            self._attached_port.execute(com)

    def doIsAllowed(self, target):
        if target == 'on':
            freq = self._attached_freq.read(0)
            amp = self._attached_amp.read(0)
            if freq < 1. or amp < .1:
                return False,  'Set frequency and amplitude first'
        return True, ''

    def doStart(self, pos):
        if pos == 'on':
            self._put_pv('writepv', 1)
        else:
            self._put_pv('writepv', 0)

    def doRead(self, maxage=0):
        val = self._get_pv('readpv')
        freq = self._attached_freq.read(maxage)
        amp = self._attached_amp.read(maxage)
        if val == 0 or freq < 1. or amp < .1:
            return 'off'
        return 'on'

    def doStatus(self, maxage=0):
        pos = self.doRead(maxage)
        if pos == self.target:
            return status.OK, 'Done'
        return status.BUSY, 'Moving'
Пример #26
0
class SH_Cylinder(Moveable):
    """PUMA specific device for the shutter cylinder."""

    attached_devices = {
        'io_ref': Attach('limit switches', Readable),
        'io_air': Attach('air in the open/close direction', Moveable),
        'io_pos': Attach('position stop, if in closed position, -1', Moveable),
    }

    parameters = {
        'timedelay':
        Param('Waiting time for the movement',
              type=float,
              default=3,
              settable=True,
              mandatory=False,
              unit='s'),
    }

    def doStart(self, position):
        if position == self.read(0):
            return

        if self._checkAir() != 1:
            raise NicosError(
                self, 'No air! Please open the shutter with the '
                'button near the door.')

        self._attached_io_air.move(0)
        session.delay(self.timedelay)

        if self._attached_io_ref.read(0) != 1:
            raise NicosError(self, 'Cannot close the shutter!')

        if position != -1:
            self._attached_io_pos.move(position)
            session.delay(self.timedelay)
            self._attached_io_air.move(1)
            session.delay(self.timedelay)

    def doRead(self, maxage=0):
        if self._attached_io_ref.read(0) == 1:
            return -1
        if self._attached_io_air.read(0) == 1:
            return self._attached_io_pos.read(0)

    def _checkAir(self):
        if self._attached_io_ref.read(0) == 1:
            self._attached_io_air.move(1)
            session.delay(self.timedelay)
            if self._attached_io_ref.read(0) == 1:
                return 0
        return 1
Пример #27
0
class Shutter(HasTimeout, Moveable):
    """Controlling the shutter."""

    valuetype = oneof('closed', 'open')

    parameters = {
        'waittime':
        Param('Additional time to wait before open',
              settable=True,
              unit='s',
              default=0),
    }

    attached_devices = {
        'output': Attach('output bits', Moveable),
        'input': Attach('input bits', Readable),
    }

    parameter_overrides = {
        'fmtstr': Override(default='%s'),
        'timeout': Override(default=10),
        'unit': Override(mandatory=False, default=''),
    }

    def doStatus(self, maxage=0):
        inputstatus = self._attached_input.read(maxage)
        if inputstatus == READ_OPEN:
            if self.target == 'open':
                return status.OK, 'idle'
            else:
                return status.WARN, 'open, but target=closed'
        elif inputstatus == READ_CLOSED:
            if self.target == 'closed':
                return status.OK, 'idle'
            else:
                return status.WARN, 'closed, but target=open'
        # HasTimeout will check for target reached
        return status.OK, 'idle'

    def doRead(self, maxage=0):
        inputstatus = self._attached_input.read(maxage)
        if inputstatus == READ_OPEN:
            return 'open'
        elif inputstatus == READ_CLOSED:
            return 'closed'
        return 'unknown'

    def doStart(self, target):
        session.delay(self.waittime)
        if target == 'open':
            self._attached_output.start(WRITE_OPEN)
        else:
            self._attached_output.start(WRITE_CLOSED)
Пример #28
0
class PGFilter(Moveable):
    """PUMA specific device for the PG filter."""

    attached_devices = {
        'io_status': Attach('status of the limit switches', Readable),
        'io_set': Attach('output to set', Moveable),
    }

    valuetype = oneof('in', 'out')

    def doStart(self, position):
        try:

            if self.doStatus()[0] != status.OK:
                raise NicosError(self, 'filter returned wrong position')

            if position == self.read(0):
                return

            if position == 'in':
                self._attached_io_set.move(1)
            elif position == 'out':
                self._attached_io_set.move(0)
            else:
                # shouldn't happen...
                self.log.info('PG filter: illegal input')
                return

            session.delay(2)

            if self.doStatus()[0] == status.ERROR:
                raise NicosError(
                    self, 'PG filter is not readable, please '
                    'check device!')
        finally:
            self.log.info('PG filter: %s', self.read(0))

    def doRead(self, maxage=0):
        result = self._attached_io_status.doRead(0)
        if result == 2:
            return 'in'
        elif result == 1:
            return 'out'
        else:
            raise NicosError(self, 'PG filter is not readable, check device!')

    def doStatus(self, maxage=0):
        s = self._attached_io_status.doRead(0)
        if s in [1, 2]:
            return (status.OK, 'idle')
        else:
            return (status.ERROR, 'filter is in error state')
Пример #29
0
class VSState(Moveable):
    """
    This class switches the velocity selector on and off and
    checks if the thing can actually run
    """
    attached_devices = {
        'on': Attach('Writable I/O for switching on the VS',
                     Moveable),
        'off': Attach('Writable I/O for switching off the VS',
                      Moveable),
        'state': Attach('Readable I/O for the runable state of the VS',
                        Readable),
        'hand': Attach('Readable I/O for manual state VS',
                       Readable),
        'ready': Attach('Readable I/O for the ready state of the VS',
                        Readable),
    }

    parameter_overrides = {
        'unit': Override(mandatory=False),
    }

    _target = None

    def isAllowed(self, pos):
        if self._attached_ready.read(0) == 0:
            return False, 'VS not switched on'
        if self._attached_hand.read(0) == 1:
            return False, 'VS is in manual mode'
        if pos in ['on', 'off']:
            return True, ''
        return False, '%s not allowed, only on/off permitted'

    def doRead(self, maxage=0):
        if self._attached_state.read(maxage) == 1:
            return 'on'
        return 'off'

    def doStart(self, pos):
        self._target = pos
        if pos == 'on':
            self._attached_on.start(1)
            return
        self._attached_off.start(1)

    def doStatus(self, maxage=0):
        cur = self.read(maxage)
        if cur == self._target:
            self._attached_on.start(0)
            self._attached_off.start(0)
            return status.OK, 'Done'
        return status.BUSY, 'Waiting ...'
Пример #30
0
class Beamstop(Moveable):
    """Switcher for the beamstop position.

    This switches the beamstop in or out; the "out" value is determined by
    the current resolution preset.
    """

    valuetype = oneof('out', 'in')

    attached_devices = {
        'moveable': Attach('Beamstop motor', HasPrecision),
        'resolution': Attach('Resolution device', Resolution),
    }

    parameters = {
        'outpos': Param('Position for "out"'),
    }

    parameter_overrides = {
        'unit': Override(mandatory=False, default=''),
    }

    def doRead(self, maxage=0):
        movpos = self._attached_moveable.read(maxage)
        if abs(movpos - self.outpos) <= self._attached_moveable.precision:
            return 'out'
        respos = self._attached_resolution.read(maxage)
        if respos != 'unknown':
            inpos = self._attached_resolution.presets[respos]['beamstop_x_in']
            if abs(movpos - inpos) <= self._attached_moveable.precision:
                return 'in'
        return 'unknown'

    def doStatus(self, maxage=0):
        code, text = Moveable.doStatus(self, maxage)
        if code == status.OK and self.read(maxage) == 'unknown':
            return status.NOTREACHED, 'unknown position'
        return code, text

    def _getWaiters(self):
        return [self._attached_moveable]

    def doStart(self, pos):
        if pos == 'out':
            self._attached_moveable.start(self.outpos)
            return
        respos = self._attached_resolution.target
        if respos != 'unknown':
            inpos = self._attached_resolution.presets[respos]['beamstop_x_in']
            self._attached_moveable.start(inpos)
        else:
            raise MoveError('no position for beamstop, resolution is unknown')