Exemplo n.º 1
0
class SelectorSwitcher(KWS1SelectorSwitcher):
    """Switcher whose mapping is determined by a list of presets."""

    parameter_overrides = {
        'presets':
        Override(type=dictof(
            str, dictwith(lam=float, speed=float, spread=float, tilted=bool))),
    }
Exemplo n.º 2
0
class SelectorSwitcher(MultiSwitcher):
    """Switcher whose mapping is determined by a list of presets."""

    parameters = {
        'presets':
        Param('Presets that determine the mapping',
              type=dictof(str, dictwith(lam=float, speed=float, spread=float)),
              mandatory=True),
    }

    attached_devices = {
        'det_pos': Attach('Detector preset device', DetectorPosSwitcherMixin),
    }

    def doUpdateValue(self, position):
        self._attached_det_pos._updateMapping(position)

    def _getWaiters(self):
        return self._attached_moveables

    def _start_unchecked(self, position):
        MultiSwitcher._start_unchecked(self, position)
        self._attached_det_pos._updateMapping(position)
Exemplo n.º 3
0
class SingleSlit(PseudoNOK, HasOffset, Moveable):
    """Slit using one axis."""

    hardware_access = False

    attached_devices = {
        'motor': Attach('moving motor', Moveable),
    }

    parameters = {
        'mode':
        Param('Beam mode',
              type=oneof(*MODES),
              settable=True,
              userparam=True,
              default='slit',
              category='general'),
        '_offsets':
        Param('List of offsets per mode position',
              settable=False,
              internal=True,
              type=dictof(str, float),
              default={}),
        'opmode':
        Param('Mode of operation for the slit',
              type=oneof(CENTERED),
              userparam=True,
              settable=True,
              default=CENTERED,
              category='experiment'),
    }

    parameter_overrides = {
        'masks':
        Override(type=dictwith(**{name: float
                                  for name in MODES}),
                 unit='',
                 mandatory=True),
    }

    valuetype = float

    def doWriteOffset(self, value):
        HasOffset.doWriteOffset(self, value)
        # deep copy is need to be able to change the values
        d = self._offsets.copy()
        d[self.mode] = value
        self._setROParam('_offsets', d)

    def doRead(self, maxage=0):
        return self._attached_motor.read(maxage) - self.masks[self.mode] - \
            self.offset

    def doIsAllowed(self, target):
        return self._attached_motor.isAllowed(target + self.masks[self.mode])

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

    def doStart(self, target):
        self._attached_motor.start(target + self.masks[self.mode] +
                                   self.offset)

    def doWriteMode(self, mode):
        self._attached_motor.start(
            self._attached_motor.read(0) + self.masks[mode] -
            self.masks[self.mode])
        # update the offset parameter from offset mapping
        self._setROParam('offset', self._offsets.get(mode, 0.))
        self.log.debug('New offset is now: %f', self.offset)
Exemplo n.º 4
0
class HasSwitchPv(DeviceMixinBase):
    """
    A mixin that can be used with EPICS based devices.

    Devices that inherit this mixin get a new property that indicates
    whether the device is switched on (that may mean different things
    in different devices):

        dev.isSwitchedOn

    To switch the device on or off, use the provided methods:

        dev.switchOn()
        dev.switchOff()

    The link to EPICS is configured via the switchpvs and switchstates
    parameters. The former defines which PV to read for the status
    information as well as which one to write to when using the methods.
    The latter defines what values the PV accepts for on and off
    respectively.
    """

    parameters = {
        'switchstates':
        Param('Map of boolean switch states to underlying type',
              type=dictwith(on=anytype, off=anytype),
              userparam=False),
        'switchpvs':
        Param('Read and write pv for switching device on and off.',
              type=dictwith(read=pvname, write=pvname),
              userparam=False)
    }

    def _get_pv_parameters(self):
        # Use colon prefix to prevent name clashes with
        # PVs specified in EpicsDevice.param
        switch_pvs = {'switchpv:' + pv for pv in self.switchpvs}

        return super(HasSwitchPv, self)._get_pv_parameters() | switch_pvs

    def _get_pv_name(self, pvparam):
        components = pvparam.split(':', 1)

        if len(components) == 2 and components[0] == 'switchpv':
            return self.switchpvs[components[1]]

        return super(HasSwitchPv, self)._get_pv_name(pvparam)

    @property
    def isSwitchedOn(self):
        """
        True if the device is switched on.
        """
        raw_value = self._get_pv('switchpv:read')

        if raw_value not in self.switchstates.values():
            self.log.warning('State by attached switch device not recognized. '
                             'Returning raw value.')

            return raw_value

        return raw_value == self.switchstates['on']

    @usermethod
    def switchOn(self):
        """
        Switch the device on (writes the 'on' of switchstates map to the
        write-pv specified in switchpvs).
        """
        if not self.isSwitchedOn:
            self._put_pv('switchpv:write', self.switchstates['on'])
        else:
            self.log.info('Device is already switched on')

    @usermethod
    def switchOff(self):
        """
        Switch the device off (writes the 'off' of switchstates map to the
        write-pv specified in switchpvs).
        """
        if self.isSwitchedOn:
            self._put_pv('switchpv:write', self.switchstates['off'])
        else:
            self.log.info('Device is already switched off')
Exemplo n.º 5
0
class DetectorPosSwitcher(DetectorPosSwitcherMixin, SequencerMixin,
                          MappedMoveable):
    """Switcher for the detector and detector position.

    This controls the large/small detector and the X/Y/Z components of the
    detector position.  Presets depend on the target wavelength given by the
    selector.
    """

    hardware_access = False

    attached_devices = {
        'det_z': Attach('Large detector Z axis', Moveable),
        'bs_x': Attach('Large detector beamstop X axis', Moveable),
        'bs_y': Attach('Large detector beamstop Y axis', Moveable),
        'psd_x': Attach('Small detector X axis', Moveable),
        'psd_y': Attach('Small detector Y axis', Moveable),
        'attenuator': Attach('Beam attenuator', Moveable),
    }

    parameters = {
        'presets':
        Param('Presets that determine the mappings',
              type=dictof(
                  str,
                  dictof(
                      str,
                      dictwith(x=float,
                               y=float,
                               z=float,
                               attenuator=str,
                               small=bool))),
              mandatory=True),
        'offsets':
        Param(
            'Offsets to correct TOF chopper-detector length '
            'for the errors in the det_z axis value',
            type=dictof(float, float),
            mandatory=True),
        'mapkey':
        Param('Last selector position for mapping',
              type=str,
              settable=True,
              internal=True),
        'psdtoppos':
        Param('"Top" end position of small detector',
              unit='mm',
              mandatory=True),
        'detbackpos':
        Param('"Back" end position of large detector',
              unit='m',
              mandatory=True),
    }

    parameter_overrides = {
        'mapping': Override(mandatory=False, settable=True, internal=True),
        'fallback': Override(userparam=False, type=str, mandatory=True),
    }

    def doInit(self, mode):
        # check that an offset is defined for each z distance
        for _selpos, selpresets in self.presets.items():
            for _pname, preset in selpresets.items():
                if preset['z'] not in self.offsets:
                    raise ConfigurationError(
                        self, 'no detector offset found in configuration '
                        'for detector distance of %.2f m' % preset['z'])
        MappedMoveable.doInit(self, mode)
        # apply mapping of last selector pos in case it changed
        if mode == MASTER:
            self._updateMapping(self.mapkey)

    def _updateMapping(self, selpos):
        self.log.debug('updating the detector mapping for selector '
                       'setting %s' % selpos)
        try:
            pos = self.presets.get(selpos, {})
            new_mapping = {
                k: [v['x'], v['y'], v['z'], v['small'], v['attenuator']]
                for (k, v) in pos.items()
            }
            self.mapping = new_mapping
            self.mapkey = selpos
            self.valuetype = oneof_detector(*sorted(new_mapping, key=num_sort))
            if self._cache:
                self._cache.invalidate(self, 'value')
                self._cache.invalidate(self, 'status')
        except Exception:
            self.log.warning('could not update detector mapping', exc=1)

    def _startRaw(self, pos):
        if self._seq_is_running():
            if self._mode == SIMULATION:
                self._seq_thread.join()
                self._seq_thread = None
            else:
                raise MoveError(
                    self, 'Cannot start device, sequence is still '
                    'running (at %s)!' % self._seq_status[1])

        # switch det_img alias synchronously, the chopper sets its mode param!
        det_img_alias = session.getDevice('det_img')
        if pos[3]:
            det_img_alias.alias = 'det_img_jum'
        else:
            det_img_alias.alias = 'det_img_ge'

        seq = []
        seq.append(SeqDev(self._attached_attenuator, pos[4]))
        if pos[3]:
            seq.append(
                SeqDev(self._attached_det_z, self.detbackpos, stoppable=True))
            seq.append(SeqDev(self._attached_psd_y, pos[1], stoppable=True))
            seq.append(SeqDev(self._attached_psd_x, pos[0], stoppable=True))
        else:
            seq.append(SeqDev(self._attached_psd_x, 0, stoppable=True))
            seq.append(
                SeqDev(self._attached_psd_y, self.psdtoppos, stoppable=True))
            seq.append(SeqDev(self._attached_bs_y, pos[1], stoppable=True))
            seq.append(SeqDev(self._attached_bs_x, pos[0], stoppable=True))
            seq.append(SeqDev(self._attached_det_z, pos[2], stoppable=True))
            # maybe reposition beamstop Y axis to counter jitter.
            seq.append(SeqCall(self._check_bsy, pos[1]))

        self._startSequence(seq)

    def _check_bsy(self, target):
        bsy = self._attached_bs_y
        for _i in range(5):
            if self._seq_stopflag:
                return
            readings = []
            for _j in range(20):
                readings.append(bsy.read(0))
                session.delay(0.2)
            if all(abs(v - target) <= bsy.precision for v in readings):
                return  # it's ok
            self.log.warning(
                'beamstop not ok, waiting 60 seconds for repositioning')
            session.delay(60)
            if self._seq_stopflag:
                return
            bsy.maw(target)
        self.log.error('beamstop not ok after 5 reposition tries')

    def _readRaw(self, maxage=0):
        return {
            n: (d.read(maxage), getattr(d, 'precision', 0))
            for (n, d) in self._adevs.items()
        }

    def _mapReadValue(self, pos):
        def eq(posname, val):
            return abs(pos[posname][0] - val) <= pos[posname][1]

        for name, values in self.mapping.items():
            if pos['attenuator'][0] != values[4]:
                continue
            if values[3]:
                if not eq('det_z', self.detbackpos):
                    continue
                if eq('psd_x', values[0]) and eq('psd_y', values[1]):
                    return name
            else:
                if not eq('psd_y', self.psdtoppos):
                    continue
                if eq('det_z', values[2]) and eq('bs_x', values[0]) and \
                   eq('bs_y', values[1]):
                    return name
        return self.fallback

    def doStatus(self, maxage=0):
        seq_status = SequencerMixin.doStatus(self, maxage)
        if seq_status[0] not in (status.OK, status.WARN):
            return seq_status
        return MappedMoveable.doStatus(self, maxage)

    def doReset(self):
        multiReset(self._adevs)

    def doStop(self):
        multiStop(self._adevs)
Exemplo n.º 6
0
 def doPreinit(self, mode):
     self._status_devs = ['soz', 'com', 'cox', 'coz', 'd1b', 'd2b', 'd3b',
                          'd4b', 'aoz', 'aom']
     InterfaceLogicalMotorHandler.doPreinit(self, mode)
     self.valuetype = dictwith(m2t=float, s2t=float, ath=float)
Exemplo n.º 7
0
class ChopperMaster(CanReference, BaseSequencer):

    valuetype = dictwith(
        wlmin=float,
        wlmax=float,
        gap=float,
        chopper2_pos=intrange(1, 6),
        D=float,
    )

    parameters = {
        'mode':
        Param('Chopper operation mode (normal, virtual6)',
              type=oneof('normal_mode', 'virtual_disc2_pos_6'),
              settable=False,
              category='status'),
        'delay':
        Param('delay for Startsignal in Degree',
              type=floatrange(-360, 360),
              settable=True,
              userparam=True),
        'wlmin':
        Param('Wavelength min',
              type=floatrange(0, 30),
              settable=True,
              userparam=True,
              unit='AA',
              category='status'),
        'wlmax':
        Param('Wavelength max',
              type=floatrange(0, 30),
              settable=True,
              userparam=True,
              unit='AA',
              category='status'),
        'dist':
        Param('flight path (distance chopper disc 1 to detector)',
              type=floatrange(0),
              settable=True,
              userparam=True,
              unit='m',
              category='status'),
        'gap':
        Param('Gap ... ',
              type=floatrange(0, 100),
              settable=True,
              userparam=True,
              unit='%',
              category='status'),
        'resolution':
        Param('Resolution ...',
              type=intrange(1, 6),
              settable=False,
              userparam=True,
              mandatory=False,
              volatile=True,
              category='status'),
        'speed':
        Param('Chopper1 speed ... ',
              type=float,
              settable=False,
              userparam=True,
              mandatory=False,
              volatile=True,
              category='status'),
    }

    _max_disks = 6

    attached_devices = {
        'chopper1': Attach('chopper1 defining speed', Moveable),
        'chopper2': Attach('chopper2 phase', Moveable),
        'chopper3': Attach('chopper3 phase also height', Moveable),
        'chopper4': Attach('chopper4 phase also height', Moveable),
        'chopper5': Attach('chopper5 phase half speed', Moveable),
        'chopper6': Attach('chopper6 phase half speed', Moveable),
        'shutter': Attach('Shutter device', Moveable),
    }

    def doInit(self, mode):
        self._choppers = (self._attached_chopper1, self._attached_chopper2,
                          self._attached_chopper3, self._attached_chopper4,
                          self._attached_chopper5, self._attached_chopper6)

    def _generateSequence(self, target):
        self.wlmin, self.wlmax = limits(
            (target.get('wlmin', self.wlmin), target.get('wlmax', self.wlmax)))
        self.dist = target.get('D', self.dist)
        self.gap = target.get('gap', self.gap)
        chopper2_pos = target.get('chopper2_pos')

        speed, angles = chopper_config(self.wlmin,
                                       self.wlmax,
                                       self.dist,
                                       chopper2_pos,
                                       gap=self.gap)

        self.log.debug('speed: %d, angles = %s', speed, angles)

        seq = []
        shutter_pos = self._attached_shutter.read(0)
        shutter_ok = self._attached_shutter.status(0)[0] == status.OK
        if chopper2_pos == 6:
            self._setROParam('mode', 'virtual_disc2_pos_6')
        else:
            self._setROParam('mode', 'normal_mode')
            chopper2_pos_akt = self._attached_chopper2.pos
            if chopper2_pos_akt != chopper2_pos:
                if shutter_ok:
                    seq.append(
                        SeqDev(self._attached_shutter,
                               'closed',
                               stoppable=True))
                seq.append(SeqDev(self._attached_chopper1, 0, stoppable=True))
                seq.append(
                    SeqSlowParam(self._attached_chopper2, 'pos', chopper2_pos))

        for dev, t in zip(self._choppers[1:], angles[1:]):
            # The Chopper measures the phase in the opposite direction
            # as we do this was catered for here, we have moved the
            # sign conversion to the doWritePhase function
            # dev.phase = -t  # sign by history
            seq.append(SeqFuzzyParam(dev, 'phase', t, 0.5))
        seq.append(SeqDev(self._attached_chopper1, speed, stoppable=True))
        if shutter_ok:
            seq.append(
                SeqDev(self._attached_shutter, shutter_pos, stoppable=True))
        return seq

    def doRead(self, maxage=0):
        # TODO: for cfg
        value = {
            'D':
            self.dist,
            'wlmin':
            self.wlmin,
            'wlmax':
            self.wlmax,
            'gap':
            self.gap,
            'chopper2_pos':
            self._attached_chopper2.pos if self.mode == 'normal_mode' else 6,
        }
        return value

    def _getWaiters(self):
        return self._choppers

    def doReadResolution(self):
        if self.mode == 'normal_mode':
            return self._attached_chopper2.pos
        else:
            return 6

    def doReadSpeed(self):
        return self._attached_chopper1.read(0)
Exemplo n.º 8
0
class DoubleMotorNOK(SequencerMixin, CanReference, PseudoNOK, HasPrecision,
                     Moveable):
    """NOK using two axes.

    If backlash is negative, approach form the negative side (default),
    else approach from the positive side.
    If backlash is zero, don't mind and just go to the target.
    """

    attached_devices = {
        'motor_r': Attach('NOK moving motor, reactor side', Moveable),
        'motor_s': Attach('NOK moving motor, sample side', Moveable),
    }

    parameters = {
        'mode':
        Param('Beam mode',
              type=oneof(*MODES),
              settable=True,
              userparam=True,
              default='ng',
              category='experiment'),
        'nok_motor':
        Param('Position of the motor for this NOK',
              type=tupleof(float, float),
              settable=False,
              unit='mm',
              category='general'),
        'inclinationlimits':
        Param('Allowed range for the positional '
              'difference',
              type=limits,
              mandatory=True),
        'backlash':
        Param('Backlash correction in phys. units',
              type=float,
              default=0.,
              unit='main'),
        'offsets':
        Param('Offsets of NOK-Motors (reactor side, sample side)',
              type=tupleof(float, float),
              default=(0., 0.),
              settable=False,
              unit='main',
              category='offsets'),
    }

    parameter_overrides = {
        'precision':
        Override(type=floatrange(0, 100)),
        'masks':
        Override(type=dictwith(**{name: float
                                  for name in MODES}),
                 unit='',
                 mandatory=True),
    }

    valuetype = tupleof(float, float)
    _honor_stop = True

    @lazy_property
    def _devices(self):
        return self._attached_motor_r, self._attached_motor_s

    def doInit(self, mode):
        for dev in self._devices:
            if hasattr(dev, 'backlash') and dev.backlash != 0:
                self.log.warning(
                    'Attached Device %s should not have a '
                    'non-zero backlash!', dev)

    def doRead(self, maxage=0):
        return [
            dev.read(maxage) - ofs - self.masks[self.mode]
            for dev, ofs in zip(self._devices, self.offsets)
        ]

    def doIsAllowed(self, targets):
        target_r, target_s = targets
        target_r += self.offsets[0]
        target_s += self.offsets[1]

        incmin, incmax = self.inclinationlimits

        inclination = target_s - target_r
        if not incmin <= inclination <= incmax:
            return False, 'Inclination %.2f out of limit (%.2f, %.2f)!' % (
                inclination, incmin, incmax)

        for dev in self._devices:
            res = dev.isAllowed(target_r)
            if not res[0]:
                return res

        # no problems detected, so it should be safe to go there....
        return True, ''

    def doIsAtTarget(self, targets):
        traveldists = [
            target - (akt + ofs)
            for target, akt, ofs in zip(targets, self.read(0), self.offsets)
        ]
        self.log.debug('doIsAtTarget', targets, 'traveldists', traveldists)
        return max(abs(v) for v in traveldists) <= self.precision

    def doStop(self):
        SequencerMixin.doStop(self)
        for dev in self._devices:
            dev.stop()
        try:
            self.wait()
        finally:
            self.reset()

    def doStart(self, targets):
        """Generate and start a sequence if none is running.

        The sequence is optimised for negative backlash.
        It will first move both motors to the lowest value of
        (target + backlash, current_position) and then
        to the final target.
        So, inbetween, the NOK should be parallel to the beam.
        """
        if self._seq_is_running():
            raise MoveError(self, 'Cannot start device, it is still moving!')

        # check precision, only move if needed!
        if self.isAtTarget(targets):
            return

        devices = self._devices

        # XXX: backlash correction and repositioning later

        # build a moving sequence
        sequence = []

        # now go to target
        sequence.append([
            SeqDev(d, t + ofs + self.masks[self.mode], stoppable=True)
            for d, t, ofs in zip(devices, targets, self.offsets)
        ])

        # now go to target again
        sequence.append([
            SeqDev(d, t + ofs + self.masks[self.mode], stoppable=True)
            for d, t, ofs in zip(devices, targets, self.offsets)
        ])

        self.log.debug('Seq: %r', sequence)
        self._startSequence(sequence)

    def doReset(self):
        multiReset(self._motors)
Exemplo n.º 9
0
class Doppler(SequencerMixin, MultiSwitcher):
    """Device to change the dopplerspeed.
    It also compares the speed and amplitude 'seen' by the SIS detector to
    the values set in the doppler and notifies the user if these values do
    not match."""

    parameters = {
        'margins':
        Param('margin for readout errors in refdevices',
              dictwith(speed=float, amplitude=float),
              default=dict(speed=.0, amplitude=.0),
              settable=True),
        'maxacqdelay':
        Param(
            'maximum time to wait for the detector to adjust '
            'when a measurement is started in seconds',
            int,
            default=50,
            settable=True),
        'customrange':
        Param('min and max for custom values',
              tuple,
              settable=False,
              volatile=True)
    }

    attached_devices = {
        'switch': Attach('The on/off switch of the doppler',
                         NamedDigitalOutput),
        'acq': Attach('The doppler as seen by the SIS-Detector', AcqDoppler),
    }

    def doInit(self, mode):
        MultiSwitcher.doInit(self, mode)

        self._sm_values = sorted(
            [x for x in self.mapping if isinstance(x, number_types)])[1:]

        named_vals = {k: v[0] for k, v in self.mapping.items()}

        self.valuetype = oneofdict_or(named_vals,
                                      floatrange(0, self._sm_values[-1]))

    def doRead(self, maxage=0):
        if self._attached_switch.read() == 'off':
            return 0
        return self._mapReadValue(self._readRaw(maxage))

    def doStart(self, target):
        # value is out of range
        if target not in self.mapping and not self.inRange(target):
            raise UsageError('Values have to be within %.2f and %.2f.' %
                             (self.customrange[0], self.customrange[-1]))

        # if acq runs in dummy mode this will change the speed accordingly
        # otherwise this will be ignored in entangle
        self._attached_acq.changeDummySpeed(target)

        # a custom value has been requested
        if target not in self.mapping:
            self.log.warning('Moving doppler to a speed which is not in the '
                             'configured setup (but within range).')

        for entry in self._sm_values:
            if target <= entry:
                self._startRaw((target, self.mapping[entry][1]))
                return

    def _startRaw(self, target):
        if self._mode != SIMULATION \
                and session.daemon_device._controller.status == STATUS_INBREAK:
            raise UsageError('Doppler speed can not be changed when script is '
                             'paused.')
        if self._seq_is_running():
            if self._mode == SIMULATION:
                self._seq_thread.join()
            else:
                self._seq_thread.join(0)
            self._seq_thread = None

        if not self.waitForAcq(10):
            return

        seq = list()
        # to change the doppler speed it has to be stopped first
        seq.append(SeqDev(self._attached_switch, 'off'))
        seq.append(SeqCall(session.delay, 0.5))
        if target[0] != 0:
            seq.append(SeqCall(MultiSwitcher._startRaw, self, target))
            seq.append(SeqDev(self._attached_switch, 'on'))

        seq.append(SeqCall(self.waitForSync, target))
        self._startSequence(seq)

    def acqIsCounting(self):
        return self._attached_acq.status() == (status.BUSY, 'counting')

    def waitForAcq(self, retries):
        if not self.acqIsCounting():
            return True

        while retries:
            sleep(0.5)
            retries -= 1
            if not self.acqIsCounting():
                return True
        self.log.warning('Doppler speed can not be changed while '
                         'SIS is counting.')
        return False

    def waitForSync(self, target):
        if session.mode == SIMULATION:
            return

        elif target[0] == 0:
            while True:
                if self.withinMargins(self._attached_acq.read()[0], 0, SPEED):
                    return
                session.delay(1)
        else:
            while True:
                acq_speed, acq_ampl = self._attached_acq.read()
                speed, ampl = self._readRaw()
                # acq and doppler are back in sync after changing the doppler
                if self.withinMargins(acq_speed, speed, SPEED) \
                        and self.withinMargins(acq_ampl, ampl, AMPLITUDE):
                    return
                session.delay(1)

    def withinMargins(self, value, target, name):
        return target - self.margins[name] < value < target + self.margins[name]

    def doStatus(self, maxage=0):
        # when the sequence is running only it's status is of interest
        if self._seq_status[0] != status.OK:
            return self._seq_status

        # otherwise the actual doppler status is relevant
        acq_speed, acq_ampl = self._attached_acq.read()
        speed, ampl = self._readRaw()

        if self._attached_switch.read() == 'off':
            if not self.withinMargins(acq_speed, 0, SPEED):
                return (status.WARN, 'detector registers movement of the '
                        'doppler, although it has been stopped.')
        elif not self.withinMargins(acq_speed, speed, SPEED):
            return (status.WARN, 'detector observes a speed differing '
                    'from the dopplerspeed')
        elif not self.withinMargins(acq_ampl, ampl, AMPLITUDE):
            return (status.WARN, 'detector observes an amplitude differing '
                    'from the doppleramplitude')
        elif round(speed, 2) not in self._sm_values \
                and self.inRange(speed):
            return status.OK, 'Doppler runs at custom speed'

        return MultiSwitcher.doStatus(self, maxage)

    def isAllowed(self, pos):
        if self.inRange(pos):
            return True, ''
        else:
            return MultiSwitcher.isAllowed(self, pos)

    def inRange(self, speed):
        return self.customrange[0] < speed < self.customrange[-1]

    def _mapReadValue(self, pos):
        value = MultiSwitcher._mapReadValue(self, pos)
        if value == self.fallback and self.inRange(pos[0]):
            value = pos[0]

        return value

    def doReadCustomrange(self):
        return self._attached_moveables[0].userlimits
Exemplo n.º 10
0
class DoubleMotorBeckhoffNOK(DoubleMotorBeckhoff):
    """NOK using two axes.
    """

    parameters = {
        'mode':
        Param('Beam mode',
              type=oneof(*MODES),
              settable=True,
              userparam=True,
              default='ng',
              category='experiment'),
        'nok_motor':
        Param('Position of the motor for this NOK',
              type=tupleof(float, float),
              settable=False,
              unit='mm',
              category='general'),
        'inclinationlimits':
        Param('Allowed range for the positional '
              'difference',
              type=limits,
              mandatory=True),
        'backlash':
        Param('Backlash correction in phys. units',
              type=float,
              default=0.,
              unit='main'),
        'offsets':
        Param('Offsets of NOK-Motors (reactor side, sample side)',
              type=tupleof(float, float),
              default=(0., 0.),
              settable=False,
              unit='main',
              category='offsets'),
    }

    parameter_overrides = {
        'precision':
        Override(type=floatrange(0, 100)),
        'masks':
        Override(type=dictwith(**{name: float
                                  for name in MODES}),
                 unit='',
                 mandatory=True),
        'address':
        Override(mandatory=False),
    }

    valuetype = tupleof(float, float)
    _honor_stop = True

    def doInit(self, mode):
        for name, idx in [('reactor', 0), ('sample', 1)]:
            self.__dict__[name] = SingleMotorOfADoubleMotorNOK(
                '%s.%s' % (self.name, name),
                unit=self.unit,
                both=self,
                lowlevel=True,
                index=idx)

    def doWriteMode(self, mode):
        self.log.debug('DoubleMotorBeckhoffNOK arg:%s  self:%s', mode,
                       self.mode)
        target = self.doRead(0)
        self.log.debug('DoubleMotorBeckhoffNOK target %s', target)
        target = [pos + self.masks[mode] for pos in target]
        self.log.debug('DoubleMotorBeckhoffNOK target %s', target)
        DoubleMotorBeckhoff.doStart(self, target)

    def doRead(self, maxage=0):
        self.log.debug('DoubleMotorBeckhoffNOK read')
        return [
            pos - self.masks[self.mode]
            for pos in DoubleMotorBeckhoff.doRead(self, maxage)
        ]

    def doIsAllowed(self, target):
        self.log.debug('DoubleMotorBeckhoffNOK doIsAllowed')
        target_r, target_s = (target[0] + self.offsets[0],
                              target[1] + self.offsets[1])

        incmin, incmax = self.inclinationlimits

        inclination = target_s - target_r
        if not incmin < inclination < incmax:
            return False, 'Inclination %.2f out of limit (%.2f, %.2f)!' % (
                inclination, incmin, incmax)

        # no problems detected, so it should be safe to go there....
        return True, ''

    def doIsAtTarget(self, pos, target):
        self.log.debug('DoubleMotorBeckhoffNOK doIsAtTarget')
        stat = self.status(0)
        if stat[0] == status.BUSY:
            return False
        return True

    def doStart(self, target):
        self.log.debug('DoubleMotorBeckhoffNOK doStart')
        self.log.debug('target %s %s', target, type(target))
        target = [pos + self.masks[self.mode] for pos in target]
        self.log.debug('target %s %s', target, type(target))
        DoubleMotorBeckhoff.doStart(self, target)
Exemplo n.º 11
0
class AmorLogicalMotorHandler(Moveable):
    """ Controller for the logical motors. This class has all the
    equations coded and can be used to read the positions of the logical
    motors or to calculate the positions of the real motors when the logical
    motor is to be moved.
    """

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

    # The real motors
    attached_devices = {
        'soz': Attach('soz motor', EpicsMotor, missingok=True),
        'com': Attach('com motor', EpicsMotor, missingok=True),
        'cox': Attach('cox motor', EpicsMotor, missingok=True),
        'coz': Attach('coz motor', EpicsMotor, missingok=True),
        'd1b': Attach('d1b motor', EpicsMotor, missingok=True),
        'd2b': Attach('d2b motor', EpicsMotor, missingok=True),
        'd3b': Attach('d3b motor', EpicsMotor, missingok=True),
        'd4b': Attach('d4b motor', EpicsMotor, missingok=True),
        'aoz': Attach('aoz motor', EpicsMotor, missingok=True),
        'aom': Attach('aom motor', EpicsMotor, missingok=True),
        'd1t': Attach('d1t motor (only to read)', EpicsMotor, missingok=True),
        'd2t': Attach('d2t motor (only to read)', EpicsMotor, missingok=True),
        'd3t': Attach('d3t motor (only to read)', EpicsMotor, missingok=True),
        'd4t': Attach('d4t motor (only to read)', EpicsMotor, missingok=True),
        'distances': Attach('Device that stores the distances',
                            DistancesHandler)
    }

    valuetype = dictwith(m2t=float, s2t=float, ath=float)

    status_devs = [
        'soz', 'com', 'cox', 'coz', 'd1b', 'd2b', 'd3b', 'd4b', 'aoz', 'aom'
    ]

    status_to_msg = {
        status.ERROR: 'Error in %s',
        status.BUSY: 'Moving: %s ...',
        status.WARN: 'Warning in %s',
        status.NOTREACHED: '%s did not reach target!',
        status.UNKNOWN: 'Unknown status in %s!',
        status.OK: 'Ready.'
    }

    def doInit(self, mode):
        # Collect all the logical motors in this variable
        self._logical_motors = {}

    def register(self, motortype, motor):
        self._logical_motors[motortype] = motor

    def doReadFmtstr(self):
        return ', '.join([mt + '=%(' + mt + ').3f' for mt in motortypes])

    def _get_dev(self, dev):
        return getattr(self, '_attached_%s' % dev, None)

    def _read_dev(self, dev):
        dev = self._get_dev(dev)
        return dev.read(0) if dev else 0.0

    def _is_active(self, component):
        distances = self._attached_distances
        return component in session.loaded_setups and \
            isinstance(getattr(distances, component), number_types)

    def doRead(self, maxage=0):
        distances = self._attached_distances

        # Check if the sample and polariser distances are available
        if not isinstance(distances.sample, number_types):
            raise PositionError('Distances for sample and polariser unknown')

        soz = self._read_dev('soz')

        if not self._is_active('polariser'):
            actm2t = 0.0
        else:
            dist = abs(distances.sample - distances.polariser)
            tmp = soz / dist if dist else 0
            actm2t = math.degrees(-1 *
                                  math.atan(tmp)) if abs(tmp) > 1e-4 else 0.0

        if self._is_active('analyser'):
            aom = self._read_dev('aom')
            aoz = self._read_dev('aoz')
            sah = abs(distances.analyser - distances.sample)
            acts2t = math.degrees(math.atan((aoz - soz) / sah)) + actm2t
            actath = -1 * (acts2t - actm2t - aom)
        else:
            com = self._read_dev('com')
            acts2t = com + actm2t
            aom = self._read_dev('aom')
            actath = aom - com

        return {
            M2T: round(actm2t, 3),
            S2T: round(acts2t, 3),
            ATH: round(actath, 3)
        }

    def doStatus(self, maxage=0):
        # Check for error and warning in the dependent devices
        devs = {
            dev: self._get_dev(dev)
            for dev in self.status_devs if self._get_dev(dev)
        }
        st_devs = multiStatus(devs, 0)
        devs = [n for n, d in iteritems(devs) if d.status()[0] == st_devs[0]]

        if st_devs[0] in self.status_to_msg:
            msg = self.status_to_msg[st_devs[0]]
            if '%' in msg:
                msg = msg % ', '.join(devs)
            return st_devs[0], msg

        return st_devs

    def doIsCompleted(self):
        # No attached devices, so have to manually check the doIsCompleted
        for dev in self.status_devs:
            dev = self._get_dev(dev)
            if dev and not dev.isCompleted():
                return False

        return True

    def doIsAllowed(self, targets):
        # Calculate the possible motor positions using these targets
        motor_targets = self._get_move_list(self._get_targets(targets))

        # Check if these positions are allowed and populate the
        # faults list with the motors that cannot be moved
        faults = []
        for motor, target in motor_targets:
            allowed, _ = motor.isAllowed(target)
            if not allowed:
                faults.append(motor.name)
                self.log.error('%s cant be moved to %s; limits are %s', motor,
                               motor.format(target, motor.unit),
                               motor.abslimits)

        # Return false if some motors cannot reach their new target
        if faults:
            return False, '%s not movable!' % ', '.join(faults)

        # Return True if everything ok
        return True, ''

    def doStart(self, targets):
        for motor, target in self._get_move_list(self._get_targets(targets)):
            self.log.debug('New target for %s: %s', motor,
                           motor.format(target, motor.unit))
            motor.move(target)

    def _get_targets(self, targets):
        targets_dict = {}
        current = self.read(0)
        for mt in motortypes:
            target = targets.get(mt)
            if target is None:
                # If the target is not valid or not specified, fetch the
                # target from motor itself
                motor = self._logical_motors.get(mt)
                if not motor:
                    self.log.debug(
                        'Missing the logical motor %s! '
                        'Using target = %s (current position) ', mt,
                        current[mt])
                    targets_dict[mt] = current[mt]
                elif motor.target is not None:
                    targets_dict[mt] = round(motor.target or current[mt], 3)
                else:
                    targets_dict[mt] = current[mt]
            else:
                targets_dict[mt] = round(target, 3)

        # Return the dictionary of motortype mapped to their targets
        return targets_dict

    def _get_move_list(self, targets):
        # Equations to calculate the positions of the real motors to be moved
        # are implemented in this function

        self.log.debug('Recalculating with targets: %s', targets)
        positions = []

        distances = self._attached_distances

        # soz
        dist = abs(distances.sample - distances.polariser)
        soz = dist * math.tan(math.radians(-1 * targets[M2T]))
        positions.append((self._get_dev('soz'), soz))

        # slit 1 is before the monochromator and does not need to be
        # driven when m2t changes. This is here to make sure that d1b
        # is in a feasible position.
        if self._is_active('slit1'):
            mot = self._read_dev('d1t')
            val = -.5 * mot
            positions.append((self._get_dev('d1b'), val))

        # slit 2
        if self._is_active('slit2'):
            dist = abs(distances.slit2 - distances.polariser)
            mot = self._read_dev('d2t')
            val = dist * math.tan(math.radians(-1 * targets[M2T])) - 0.5 * mot
            positions.append((self._get_dev('d2b'), val))

        # slit 3
        if self._is_active('slit3'):
            dist = abs(distances.slit3 - distances.polariser)
            mot = self._read_dev('d3t')
            val = dist * math.tan(math.radians(-1 * targets[M2T])) - 0.5 * mot
            positions.append((self._get_dev('d3b'), val))

        # Analyzer
        if self._is_active('analyser'):
            com = targets[S2T] - targets[M2T] + 2 * targets[ATH]
            sah = abs(distances.analyser - distances.sample)
            aoz = soz + sah * math.tan(
                math.radians(targets[S2T] - targets[M2T]))
            aom = targets[S2T] - targets[M2T] + targets[ATH]
            positions.append((self._get_dev('aoz'), aoz))
            positions.append((self._get_dev('aom'), aom))

            # Detector when analyzer present
            if self._is_active('detector'):
                sdh = abs(distances.detector - distances.sample)
                positions.append((self._get_dev('com'), com))
                tmp = soz - aoz
                sqrtsqsum = math.sqrt(sah * sah + tmp * tmp)
                val = sah - sqrtsqsum + (sdh - sqrtsqsum) * (
                    math.cos(math.radians(com)) - 1.0)
                positions.append((self._get_dev('cox'), -1 * val))
                val = aoz + (sdh - sqrtsqsum) * math.sin(math.radians(com))
                positions.append((self._get_dev('coz'), val))
        else:
            # Detector when no analyzer present
            com = targets[S2T] - targets[M2T]
            if self._is_active('detector'):
                positions.append((self._get_dev('com'), com))
                dist = abs(distances.detector - distances.sample)
                val = -1 * dist * (math.cos(math.radians(com)) - 1.0)
                positions.append((self._get_dev('cox'), val))
                val = dist * math.sin(math.radians(com)) + soz
                positions.append((self._get_dev('coz'), val))

        # slit 4
        if self._is_active('slit4'):
            dist = abs(distances.slit4 - distances.sample)
            mot = self._read_dev('d4t')
            if self._is_active('analyser'):
                val = (soz + dist *
                       math.tan(math.radians(targets[S2T] - targets[M2T])) -
                       0.5 * mot)
            else:
                val = soz + dist * math.tan(math.radians(com)) - 0.5 * mot
            positions.append((self._get_dev('d4b'), val))

        # Return rounded targets and remove unwanted positions
        return [(dev, round(targ, 3)) for dev, targ in positions if dev]
Exemplo n.º 12
0
class DetectorPosSwitcher(DetectorPosSwitcherMixin, SequencerMixin,
                          MappedMoveable):

    hardware_access = False

    attached_devices = {
        'det_z':      Attach('Large detector Z axis', Moveable),
        'bs_x':       Attach('Large detector beamstop X axis', Moveable),
        'bs_y':       Attach('Large detector beamstop Y axis', Moveable),
    }

    parameters = {
        'presets':    Param('Presets that determine the mappings',
                            type=dictof(str, dictof(str, dictwith(
                                x=float, y=float, z=float))),
                            mandatory=True),
        'offsets':    Param('Offsets to correct TOF chopper-detector length '
                            'for the errors in the det_z axis value',
                            type=dictof(float, float),
                            mandatory=True),
        'mapkey':     Param('Last selector position for mapping',
                            type=str, settable=True, internal=True),
        'beamstopsettlepos': Param('Settling position for beamstop y axis',
                                   settable=True, default=400),
    }

    parameter_overrides = {
        'mapping':  Override(mandatory=False, settable=True, internal=True),
        'fallback':  Override(userparam=False, type=str, mandatory=True),
    }

    def doInit(self, mode):
        # check that an offset is defined for each z distance
        for _selpos, selpresets in iteritems(self.presets):
            for _pname, preset in iteritems(selpresets):
                if preset['z'] not in self.offsets:
                    raise ConfigurationError(
                        self, 'no detector offset found in configuration '
                        'for detector distance of %.2f m' % preset['z'])
        MappedMoveable.doInit(self, mode)
        # apply mapping of last selector pos in case it changed
        if mode == MASTER:
            self._updateMapping(self.mapkey)

    def _updateMapping(self, selpos):
        self.log.debug('updating the detector mapping for selector '
                       'setting %s' % selpos)
        try:
            pos = self.presets.get(selpos, {})
            new_mapping = {k: [v['x'], v['y'], v['z']] for (k, v) in pos.items()}
            self.mapping = new_mapping
            self.mapkey = selpos
            self.valuetype = oneof_detector(*sorted(new_mapping, key=num_sort))
            if self._cache:
                self._cache.invalidate(self, 'value')
                self._cache.invalidate(self, 'status')
        except Exception:
            self.log.warning('could not update detector mapping', exc=1)

    def _startRaw(self, pos):
        if self._seq_is_running():
            if self._mode == SIMULATION:
                self._seq_thread.join()
                self._seq_thread = None
            else:
                raise MoveError(self, 'Cannot start device, sequence is still '
                                      'running (at %s)!' % self._seq_status[1])

        seq = []
        seq.append(SeqDev(self._attached_bs_y, pos[1], stoppable=True))
        seq.append(SeqDev(self._attached_bs_x, pos[0], stoppable=True))
        seq.append(SeqDev(self._attached_det_z, pos[2], stoppable=True))

        # if z has to move, reposition beamstop y afterwards by going to
        # some other value (damping vibrations) and back
        if abs(self._attached_det_z.read(0) - pos[2]) > self._attached_det_z.precision:
            seq.append(SeqDev(self._attached_bs_y, self.beamstopsettlepos, stoppable=True))
            seq.append(SeqSleep(30))
            seq.append(SeqDev(self._attached_bs_y, pos[1], stoppable=True))

        self._startSequence(seq)

    def _readRaw(self, maxage=0):
        return {n: (d.read(maxage), getattr(d, 'precision', 0))
                for (n, d) in self._adevs.items()}

    def _mapReadValue(self, pos):
        def eq(posname, val):
            return abs(pos[posname][0] - val) <= pos[posname][1]

        for name, values in iteritems(self.mapping):
            if eq('det_z', values[2]) and eq('bs_x', values[0]) and \
               eq('bs_y', values[1]):
                return name
        return self.fallback

    def doStatus(self, maxage=0):
        seq_status = SequencerMixin.doStatus(self, maxage)
        if seq_status[0] not in (status.OK, status.WARN):
            return seq_status
        return MappedMoveable.doStatus(self, maxage)

    def doReset(self):
        multiReset(self._adevs)

    def doStop(self):
        multiStop(self._adevs)
Exemplo n.º 13
0
 def doPreinit(self, mode):
     self._status_devs = ['slit1', 'slit2', 'slit2z', 'slit3', 'slit3z']
     InterfaceLogicalMotorHandler.doPreinit(self, mode)
     self.valuetype = dictwith(div=float, did=float, dih=float)
Exemplo n.º 14
0
class HasDisablePv(CanDisable):
    """
    A mixin that can be used with EPICS based devices.

    Devices that inherit this mixin get a new property that indicates
    whether the device is enabled (that may mean different things
    in different devices):

        dev.isEnabled

    To enable or disable, use the provided methods:

        dev.enable()
        dev.disable()

    The link to EPICS is configured via the switchpvs and switchstates
    parameters. The former defines which PV to read for the status
    information as well as which one to write to when using the methods.
    The latter defines what values the PV accepts for enable and disable
    respectively.
    """

    parameters = {
        'switchstates':
        Param('Map of boolean states to underlying type',
              type=dictwith(enable=anytype, disable=anytype),
              userparam=False),
        'switchpvs':
        Param('Read and write pv for enabling and disabling the device',
              type=dictwith(read=pvname, write=pvname),
              userparam=False)
    }

    def _get_pv_parameters(self):
        # Use colon prefix to prevent name clashes with
        # PVs specified in EpicsDevice.param
        switch_pvs = {'switchpv:' + pv for pv in self.switchpvs}

        return super()._get_pv_parameters() | switch_pvs

    def _get_pv_name(self, pvparam):
        components = pvparam.split(':', 1)

        if len(components) == 2 and components[0] == 'switchpv':
            return self.switchpvs[components[1]]

        return super()._get_pv_name(pvparam)

    @property
    def isEnabled(self):
        """
        True if the device is switched on.
        """
        raw_value = self._get_pv('switchpv:read')

        if raw_value not in self.switchstates.values():
            raise ConfigurationError('State by attached switch device not '
                                     'recognized.')

        return raw_value == self.switchstates['enable']

    @usermethod
    def enable(self):
        """
        Switch the device on (writes the 'enable' of switchstates map to the
        write-pv specified in switchpvs).
        """
        if not self.isEnabled:
            self._put_pv('switchpv:write', self.switchstates['enable'])
        else:
            self.log.info('Device is already switched on')

    @usermethod
    def disable(self):
        """
        Switch the device off (writes the 'disable' of switchstates map to the
        write-pv specified in switchpvs).
        """
        if self.isEnabled:
            self._put_pv('switchpv:write', self.switchstates['disable'])
        else:
            self.log.info('Device is already disabled')