Пример #1
0
class ArmController(IsController, Device):

    parameters = {
        'minangle':
        Param('Minimum angle between two arms',
              type=floatrange(0, None),
              settable=False,
              userparam=False,
              default=50.),
    }

    attached_devices = {
        'arm1': Attach('Arm 1 device', Moveable),
        'arm2': Attach('Arm 2 device', Moveable),
    }

    parameter_overrides = {
        'lowlevel': Override(default=True),
    }

    def isAdevTargetAllowed(self, adev, adevtarget):
        self.log.debug('%s: %s', adev, adevtarget)
        if adev == self._attached_arm1:
            target = self._attached_arm2.target
            if target is None:
                target = self._attached_arm2.read(0)
            absdiff = target - adevtarget
        else:
            target = self._attached_arm1.target
            if target is None:
                target = self._attached_arm1.read(0)
            absdiff = adevtarget - target
        if absdiff < 0:
            return False, 'Arms will cross.'
        dist = abs(absdiff)
        if dist >= self.minangle:
            return True, ''
        return False, 'Arms become too close to each other: %.3f deg, min. ' \
            'dist is %.3f' % (dist, self.minangle)
Пример #2
0
class HasMapping(DeviceMixinBase):
    """
    Mixin class for devices that use a finite mapping between user supplied
    input and internal representation.

    This is mainly useful for devices which can only yield certain values or go
    to positions from a predefined set, like switching devices.

    Abstract classes that use this mixin are implemented in
    `nicos.devices.abstract.MappedReadable` and `.MappedMoveable`.
    """
    parameters = {
        'mapping':
        Param('Mapping of device values to raw (internal) values',
              unit='',
              settable=False,
              mandatory=True,
              type=dictof(str, anytype)),
        'fallback':
        Param(
            'Readback value if the raw device value is not in '
            'the mapping or None to disable',
            default=None,
            unit='',
            type=anytype,
            settable=False),
    }

    # mapped values usually are string constants and have no unit
    parameter_overrides = {
        'unit': Override(mandatory=False),
    }

    def doIsAllowed(self, target):
        if target not in self.mapping:
            return False, 'unknown value: %r, must be one of %s' % \
                (target, ', '.join(map(repr, sorted(self.mapping))))
        return True, ''
Пример #3
0
class FITSImageSink(ImageSink):
    """Writes data to a FITS (Flexible image transport system) format file.

    NICOS headers are also written into the file using the standard FITS header
    facility, with HIERARCH type keys.

    Requires the pyfits library to be installed.
    """

    parameter_overrides = {
        'filenametemplate': Override(default=['%(pointcounter)08d.fits']),
    }

    handlerclass = FITSImageSinkHandler

    def doPreinit(self, _mode):
        # Stop creation of the FITSImageSink as it would make no sense
        # without pyfits.
        if pyfits is None:
            raise NicosError(self, 'pyfits module is not available. Check'
                             ' if it is installed and in your PYTHONPATH')

    def isActiveForArray(self, arraydesc):
        return len(arraydesc.shape) == 2
Пример #4
0
class CalibratedMagnet(HasLimits, Moveable):
    """Base clase for magnet supplies having an bipolar current source.

    Use this for magnets which can be calibrated, i.e. where::

        B(I) = Ic0 + c1*erf(c2*I) + c3*atan(c4*I)

    works reasonably well.
    Coefficients c0..c4 are given as 'calibration' parameter.
    """

    attached_devices = {
        'currentsource': Attach('bipolar Powersupply', Moveable),
    }

    parameters = {
        'ramp':
        Param('Target rate of field change per minute',
              unit='main/min',
              mandatory=False,
              settable=True,
              volatile=True),
        'calibration':
        Param(
            'Coefficients for calibration '
            'function: [c0, c1, c2, c3, c4] calculates '
            'B(I) = c0*I + c1*erf(c2*I) + c3*atan(c4*I)'
            ' in T',
            type=tupleof(float, float, float, float, float),
            default=(1.0, 0.0, 0.0, 0.0, 0.0),
            settable=True,
            chatty=True),
    }

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

    def _current2field(self, current, *coefficients):
        """Return field in T for given current in A.

        Should be monotonic and asymetric or _field2current will fail!

        Note: This may be overridden in derived classes.
        """
        v = coefficients or self.calibration
        if len(v) != 5:
            self.log.warning('Wrong number of coefficients in calibration '
                             'data!  Need exactly 5 coefficients!')
        return v[0]*current + v[1]*math.erf(v[2]*current) + \
            v[3]*math.atan(v[4]*current)

    def _field2current(self, field):
        """Return required current in A for requested field in T.

        Note: This may be overridden in derived classes.
        """
        # binary search/bisection
        maxcurr = self._attached_currentsource.abslimits[1]
        mincurr = -maxcurr
        maxfield = self._current2field(maxcurr)
        minfield = self._current2field(mincurr)
        if not minfield <= field <= maxfield:
            raise LimitError(
                self, 'requested field %g %s out of range %g..%g %s' %
                (field, self.unit, minfield, maxfield, self.unit))

        res = fsolve(lambda cur: self._current2field(cur) - field, 0)[0]
        self.log.debug('current for %g %s is %g', field, self.unit, res)
        return res

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

    def doStart(self, target):
        self._attached_currentsource.start(self._field2current(target))

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

    def doReset(self):
        return self._attached_currentsource.reset()

    def doReadRamp(self):
        # This is an approximation!
        return self.calibration[0] * abs(self._attached_currentsource.ramp)

    def doWriteRamp(self, newramp):
        # This is an approximation!
        self._attached_currentsource.ramp = newramp / self.calibration[0]

    def doReadAbslimits(self):
        minfield, maxfield = [
            self._current2field(I)
            for I in self._attached_currentsource.abslimits
        ]
        # include 0 in allowed range
        if minfield > 0:
            minfield = 0
        if maxfield < 0:
            maxfield = 0
        # get configured limits if any, or take max from source
        limits = self._config.get('abslimits', (minfield, maxfield))
        # in any way, clamp limits to what the source can handle
        limits = [clamp(i, minfield, maxfield) for i in limits]
        return min(limits), max(limits)

    def doWriteUserlimits(self, limits):
        HasLimits.doWriteUserlimits(self, limits)
        # all Ok, set source to max of pos/neg field current
        maxcurr = max(self._field2current(i) for i in limits)
        mincurr = min(self._field2current(i) for i in limits)
        self._attached_currentsource.userlimits = (mincurr, maxcurr)

    def doTime(self, startval, target):
        # get difference in current
        delta = abs(
            self._field2current(target) - self._field2current(startval))
        # ramp is per minute, doTime should return seconds
        return 60 * delta / self._attached_currentsource.ramp

    @usermethod
    def calibrate(self, fieldcolumn, *scannumbers):
        """Calibrate the B to I conversion, argument is one or more scan numbers.

        The first argument specifies the name of the device which should
        be used as a measuring device for the field.

        Example:

        >>> B_mira.calibrate(Bf, 351)
        """
        scans = session.experiment.data.getLastScans()
        self.log.info('determining calibration from scans, please wait...')
        Is = []
        Bs = []
        currentcolumn = self._attached_currentsource.name
        # XXX(dataapi): adapt to new Dataset class
        for scan in scans:
            if scan.counter not in scannumbers:
                continue
            if fieldcolumn not in scan.ynames or \
                    currentcolumn not in scan.xnames:
                self.log.info('%s is not a calibration scan', scan.counter)
                continue
            xindex = scan.xnames.index(currentcolumn)
            yindex = scan.ynames.index(fieldcolumn)
            yunit = scan.yunits[yindex]
            if yunit == 'T':
                factor = 1.0
            elif yunit == 'mT':
                factor = 1e-3
            elif yunit == 'uT':
                factor = 1e-6
            elif yunit == 'G':
                factor = 1e-4
            elif yunit == 'kG':
                factor = 1e-1
            else:
                raise NicosError(
                    self, 'unknown unit for B field '
                    'readout: %r' % yunit)
            for xr, yr in zip(scan.xresults, scan.yresults):
                Is.append(xr[xindex])
                Bs.append(yr[yindex] * factor)
        if not Is:
            self.log.error('no calibration data found')
            return
        fit = Fit('calibration', self._current2field,
                  ['c%d' % i for i in range(len(self.calibration))],
                  [1] * len(self.calibration))
        res = fit.run(Is, Bs, [1] * len(Bs))
        if res._failed:
            self.log.warning('fit failed')
            return
        self.calibration = res._pars[1]
Пример #5
0
class Group(Readable):

    valuetype = bool

    parameters = {
        'bitlist':
        Param(
            'Definition of a bit list',
            type=listof(str),
            settable=False,
            userparam=False,
        ),
        'okmask':
        Param(
            'Mask to define the bits results OK status',
            type=int,
            settable=False,
            userparam=False,
        ),
    }

    attached_devices = {
        'shs': Attach('shs', Readable),
    }

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

    def doReadUnit(self):
        return ''

    _register = {
        'Shutter': (0, 0),  # 0x0000
        'Ampeltest_inv': (3, 0),  # 0x0001
        'Betreten_Verboten_inv': (3, 1),  # 0x0002
        'Hupentest_inv': (3, 2),  # 0x0004
        'Schluesselschalter_Wartung_inv': (3, 3),  # 0x0008
        'Tuer_PO_auf': (3, 6),  # 0x0040
        'Tuer_PO_zu': (3, 7),  # 0x0080
        'Schnellschluss-Shutter_auf': (3, 8),  # 0x0100
        'Schnellschluss-Shutter_zu': (3, 9),  # 0x0200
        '6-fach-Shutter_auf': (3, 10),  # 0x0400
        '6-fach-Shutter_zu': (3, 11),  # 0x0800
        'Verbindung_zu_Warte_iO': (3, 12),  # 0x1000
        'Freigabe_von_Warte_fuer_ESShutter': (3, 13),  # 0x2000
        'Instrumentenverantwortlicher': (3, 14),  # 0x4000
        'Not-Aus_Kreis_inv': (3, 15),  # 0x8000
        'Verbindungstuer': (4, 8),  # 0x0100
        'Tuer_SR_auf': (4, 10),  # 0x0400
        'Tuer_SR_zu': (4, 11),  # 0x0800
        'externer_User_Kontakt_A': (5, 0),  # 0x0001
        'externer_User_Kontakt_B': (5, 1),  # 0x0002
        'PO-Aus-Schalter_1': (5, 2),  # 0x0004
        'PO-Aus-Schalter_2': (5, 4),  # 0x0008
        'Drucksensor_CB': (6, 0),  # 0x0001
        'Drucksensor_SFK': (6, 1),  # 0x0002
        'Drucksensor_Tube': (6, 2),  # 0x0004
        'Chopper_Drehzahl': (6, 3),  # 0x0008
        'Druck_service_inv': (6, 4),  # 0x0010
        'Personenschluessel_Terminal': (6, 11),  # 0x0800
        'Freigabe_Taster': (6, 12),  # 0x1000
        'Lampentest_inv': (6, 13),  # 0x2000
        'Endschalter_Ex_Shutter_inv': (6, 14),  # 0x4000
        'Handbetrieb_tube_inv': (6, 15),  # 0x8000
        'Probenort_Geraeumt_inv': (14, 2),  # 0x0004
        'Streurohr_Geraeumt_inv': (14, 3),  # 0x0008
        'IV_key_1': (15, 8),  # 0x0100
        'IV_key_2': (15, 9),  # 0x0200
        'gelb_inv': (17, 3),  # 0x0008
        'Freigabe_EIN': (17, 10),  # 0x0400
        'rot_inv': (18, 8),  # 0x0100
        'Warnschilder': (18, 9),  # 0x0200
        'Keine_Freigabe_Hub_Streurohr': (18, 10),  # 0x0400
        # nicht konzeptionell aber geht
        'Freigabe_Hub_Streurohr_inv': (18, 10),  # 0x0400
        'shutterzustand': (18, 11),  # 0x0800
        'gruen_inv': (18, 12),  # 0x0800
    }

    def _do_read_bits(self):
        res = 0
        # take values from cache to avoid to many reads
        raw = self._attached_shs.read()
        for i, key in enumerate(self.bitlist):
            address, bit = self._register[key]
            res |= ((raw[address] & (1 << bit)) >> bit) << i
        return res

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

    def doStatus(self, maxage=0):
        bits = self._do_read_bits()
        if bits == self.okmask:
            return status.OK, ''
        return status.WARN, ', '.join(
            self.format_statusbits(bits ^ self.okmask, self.bitlist))

    def format_statusbits(self, sword, labels, start=0):
        """Return a list of labels according to bit state in `sword` starting
        with bit `start` and the first label in `labels`.
        """
        smsg = []
        for i, lbl in enumerate(labels, start):
            if sword & (1 << i) and lbl:
                smsg.append(lbl)
        return smsg
Пример #6
0
class StackedAxis(HasLimits, HasPrecision, Moveable):
    """Device were two axes stacked.

    Both axes can be moved individually but the result of position and target
    is the sum of both position.  One of the axes (at the moment always the
    ``top`` axis) is preferred in use since it is cheaper (in time) to move it.

    ``HasPrecision`` is needed for the TAS monochromator device.
    """

    attached_devices = {
        'bottom': Attach('bottom axis', Moveable),
        'top': Attach('top axis', Moveable),
    }

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

    def doRead(self, maxage=0):
        self.log.debug('doRead: %s', maxage)
        return self._attached_bottom.read(maxage) + \
            self._attached_top.read(maxage)

    def doStart(self, target):
        targets = self._calc_targets(target)
        # The order is normally free to choose, but in the current application
        # the bottom device moves blocking
        self._attached_top.move(targets[1])
        self._attached_bottom.move(targets[0])

    def doIsAllowed(self, target):
        targets = self._calc_targets(target)
        for dev, t in zip([self._attached_bottom, self._attached_top],
                          targets):
            allowed = dev.isAllowed(t)
            if not allowed[0]:
                return allowed
        return True, ''

    def _calc_targets(self, target):
        """Calculate individual target positions from the sum target.

        Due to the limited range of movement of each individual axis the
        targets must be calculated for each axis.  Since the top axis is
        cheaper in time it will preferred in use and even the longer way
        if moving both.
        """
        targets = (0, 0)

        bpos = self._attached_bottom.read(0)
        lt = self._attached_top.userlimits

        # Simply move the top axis
        if lt[0] + bpos <= target <= lt[1] + bpos:
            targets = bpos, target - bpos
        else:
            tpos = self._attached_top.read(0)
            if target > (bpos + tpos):
                targets = target - lt[1], lt[1]
            else:
                targets = target - lt[0], lt[0]
        return targets

    def doReadAbslimits(self):
        return [
            sum(x) for x in zip(self._attached_bottom.abslimits,
                                self._attached_top.abslimits)
        ]
Пример #7
0
class Configuration(PyTangoDevice, PassiveChannel):
    """Channel that allows to configure various parameters of the DECTRIS
    Pilatus detector.

    Without this channel you cannot access any parameter of the Pilatus
    detector except for the exposure time (TimerChannel) out of NICOS.

    You can attach devices to this channel in order to read out their values
    and store them in the Pilatus image header via the ``mxsettings``
    parameter.
    """

    # TODO: add proper descriptions
    attached_devices = {
        'detector_distance': Attach(
            'Current detector distance to sample.',
            Readable, optional=True,
        ),
        'detector_voffset': Attach(
            'Current vertical detector translation.',
            Readable,  optional=True,
        ),
        'flux': Attach(
            'Current photon flux in cps.',
            Readable, optional=True,
        ),
        'filter_transmission': Attach(
            'Current transmission filter factor.',
            Readable, optional=True,
        ),
        'start_angle': Attach(
            '',
            Readable, optional=True,
        ),
        'detector_2theta': Attach(
            'Current two-theta position.',
            Readable, optional=True,
        ),
        'polarization': Attach(
            '',
            Readable, optional=True,
        ),
        'alpha': Attach(
            'Current alpha position.',
            Readable, optional=True,
        ),
        'kappa': Attach(
            'Current kappa position.',
            Readable, optional=True,
        ),
        'phi': Attach(
            'Current phi position.',
            Readable, optional=True,
        ),
        'chi': Attach(
            'Current chi position.',
            Readable, optional=True,
        ),
        'omega': Attach(
            'Current omega position.',
            Readable, optional=True,
        ),
        'start_position': Attach(
            '',
            Readable, optional=True,
        ),
        'shutter_time': Attach(
            '',
            Readable, optional=True,
        ),
    }

    parameters = {
        'energy': Param(
            'X-ray and threshold energy in kilo electron volt. Set to "None" '
            'if the energy is either not set yet not configurable for this '
            'detector.',
            type=none_or(
                dictwith(**dict((p, float) for p in ENERGY_PARAMETERS))),
            settable=True,
            volatile=True,
            unit='keV',
            prefercache=False,
            chatty=True,
        ),
        'exposures': Param(
            'Number of exposures to accumulate per frame/readout.',
            type=intrange(1, 2**32 - 1),
            settable=True,
            volatile=True,
            userparam=False,
            unit='',
        ),
        'imageheader': Param(
            'String to be included in the image header.',
            type=str,
            settable=True,
            volatile=True,
            unit='',
            chatty=True,
        ),
        'images': Param(
            'Number of images for an automatic sequence.',
            type=intrange(1, 2**16 - 1),
            settable=True,
            volatile=True,
            userparam=False,
            unit='',
        ),
        'mxsettings': Param(
            'Crystallographic parameters reported in the image header.',
            type=dictof(oneof(*MX_PARAMETERS), anytype),
            settable=True,
            volatile=True,
            unit='',
            prefercache=False,
        ),
        'period': Param(
            'Exposure period in seconds (must be longer than exposure time + '
            '2.28 ms readout time).',
            type=floatrange(1.0015, 60 * 24 * 60 * 60),  # maximum: 60 days
            settable=True,
            volatile=True,
            userparam=False,
            unit='s',
        ),
        'sensorvalues': Param(
            'Relative humidity and temperature sensor values on all channels.',
            type=dictof(str, str),
            unit='',
            volatile=True,
        ),
    }

    parameter_overrides = {
        'lowlevel': Override(default=True),
    }

    def _poll_all_channels(self):
        # update the status of all pilatus detector channels
        for detector in session.experiment.detectors:
            if isinstance(detector, Detector):
                detector.poll()

    def doRead(self, maxage=0):
        return []

    def valueInfo(self):
        return ()

    def doStatus(self, maxage=0):
        return PyTangoDevice.doStatus(self, maxage)[0], ''

    def doPrepare(self):
        self.doUpdateMxsettings({})

    def doReadEnergy(self):
        values = self._dev.energy
        return dict(zip(ENERGY_PARAMETERS, values)) if all(values) else None

    def _write_energy(self, value):
        # only send the energy parameters to the hardware if they have changed
        if self.energy:
            for param in ENERGY_PARAMETERS:
                if abs(value[param] - self.energy[param]) > 0.001:
                    self._dev.energy = [value['xray'], value['threshold']]
                    return

    def doWriteEnergy(self, value):
        self._write_energy(value)
        self._poll_all_channels()

    def doUpdateEnergy(self, value):
        # only necessary for transmitting setup values to the hardware
        if self.doStatus()[0] == status.OK:
            self._write_energy(value)

    def doReadExposures(self):
        return self._dev.exposures

    def doReadImages(self):
        return self._dev.images

    def doWriteImages(self, value):
        self._dev.images = value

    def doReadImageheader(self):
        return self._dev.imageHeader

    def doWriteImageHeader(self, value):
        self._dev.imageHeader = value

    def doReadMxsettings(self):
        mx_settings = self._dev.mxSettings
        if not mx_settings:
            return {}
        # create dict {k1: v1, k2: v2, ...} from list [k1, v1, k2, v2, ...]
        mx_settings = {mx_settings[2 * i]: mx_settings[2 * i + 1]
                       for i in range(len(mx_settings) // 2)}
        # convert values to tuple, float or int
        return {k: MX_PARAMETERS[k](v) for k, v in mx_settings.items()}

    def doWriteMxsettings(self, value):
        start_time = time()
        # energy update must be completed after maximum 15 seconds
        while time() < start_time + 15:
            if self.doStatus()[0] == status.OK:
                break
            self.log.info('waiting until the detector is ready')
            session.delay(1.5)
        else:
            self.log.error('mxsettings update could not be performed: '
                           'pilatus detector is still busy')
            return
        # flatten dict {k1: v1, k2: v2, ...} to [k1, v1, k2, v2, ...]
        self._dev.mxSettings = [str(v) for v in list(sum(value.items(), ()))]

    def doUpdateMxsettings(self, value):
        value = dict(value)  # convert to writable dict
        for name, adev in ((k, v) for k, v in self._adevs.items() if v):
            adev_value = adev.read()
            if name == 'filter_transmission':
                adev_value = 1 / adev_value
            value[name] = str(adev_value)
        if value:
            self.doWriteMxsettings(value)

    def doReadPeriod(self):
        return self._dev.period

    def doWritePeriod(self, value):
        self._dev.period = value

    def doReadSensorvalues(self):
        sensor_values = self._dev.sensorValues
        # create dict {k1: v1, k2: v2, ...} from list [k1, v1, k2, v2, ...]
        return {sensor_values[2 * i]: sensor_values[2 * i + 1]
                for i in range(len(sensor_values) // 2)}

    @usermethod
    @requires(level=USER)
    def setEnergy(self, radiation=None, **value):
        """Either load the predefined settings that are suitable for usage with
        silver, chromium, copper, iron oder molybdenum radiation or set
        the x-ray and threshold energy to any other appropriate values.

        :param str radiation: 'Ag', 'Cr', 'Cu', 'Fe' or 'Mo' (case insensitive)
        :param dict[str, float] value: {'xray': x, 'threshold': y}
        """
        if not (radiation or value) or radiation and value:
            raise InvalidValueError('call either dev.SetEnergy("<radiation>") '
                                    'or dev.SetEnergy(xray=x, threshold=y)')
        if radiation:
            self._dev.LoadEnergySettings(radiation)
            self._poll_all_channels()
        else:
            self.energy = value
Пример #8
0
class SANSNexusSink(NexusSink):
    parameter_overrides = {
        'settypes': Override(settable=True),
    }
Пример #9
0
class HVSwitch(SequencerMixin, MappedMoveable):
    """High voltage convenience switching device for the CHARM detector."""

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

    attached_devices = {
        'anodes':
        Attach('HV channels for the anodes', Moveable, multiple=[2, 9]),
        'banodes':
        Attach('HV channels for the boundary anodes',
               Moveable,
               multiple=[1, 8]),
        'cathodes':
        Attach('HV channels for the boundary cathodes', Moveable, multiple=2),
        'window':
        Attach('HV channel for the window', Moveable, multiple=1),
        'trip':
        Attach('Devices signaling a trip on the hardware', Readable),
    }

    parameters = {
        '_tripped':
        Param('Indicator for hardware trip',
              type=bool,
              internal=True,
              default=False),
    }

    parameter_overrides = {
        'unit': Override(default='', mandatory=False),
        'fallback': Override(default='unknown'),
        'mapping': Override(type=dictof(str, dictof(str, float))),
    }

    def doInit(self, mode):
        if self.fallback in self.mapping:
            raise ConfigurationError(
                self, 'Value of fallback parameter is '
                'not allowed to be in the mapping!')
        self._devices = {
            dev.name: dev
            for dev in (self._attached_anodes + self._attached_banodes +
                        self._attached_cathodes + self._attached_window)
        }

        if len(self._attached_anodes) != len(self._attached_banodes) + 1:
            raise ConfigurationError(
                self, 'Number of anode devices must be '
                'the number of boundary anodes + 1: %d, '
                '%d' % (len(self.anodes), len(self.banodes)))
        # if not self.relax_mapping:
        #     self.valuetype = oneof(*sorted(self.mapping, key=num_sort))

        for value in sorted(self.mapping, key=num_sort):
            try:
                self.valuetype(value)
            except ValueError as err:
                raise ConfigurationError(
                    self, '%r not allowed as key in mapping. %s' %
                    (value, err)) from err

    def doIsAllowed(self, target):
        if target == 'off':
            ok = True
        else:
            ok = not self._tripped and not self._attached_trip.read(0)
        return ok, '' if ok else 'hardware is tripped'

    def doStop(self):
        if not self._tripped:
            SequencerMixin.doStop(self)
        else:
            raise ModeError(self, "can't be stopped, device is tripped.")

    def doReset(self):
        if self._tripped:
            if self.doStatus(0)[0] == status.BUSY or self._hardware_tripped():
                raise ModeError(self,
                                "can't reset device. Hardware is tripped")
        SequencerMixin.doReset(self)
        self._setROParam('_tripped', False)

    def doPoll(self, n, maxage):
        if not self._tripped and session.sessiontype == POLLER and \
           self._hardware_tripped():
            self._setROParam('_tripped', True)

    def _hardware_tripped(self):
        return self._attached_trip.read(0) == 'Trip'

    def _generateSequence(self, target):
        anodes = self._attached_anodes + self._attached_banodes
        seq = [
            [
                SeqDev(dev, self.mapping[target][dev.name])
                for dev in self._attached_window
            ],
            [SeqDev(dev, self.mapping[target][dev.name]) for dev in anodes],
            [
                SeqDev(dev, self.mapping[target][dev.name])
                for dev in self._attached_cathodes
            ],
        ]
        return seq

    def _is_at_target(self, pos, target):
        # if values are exact the same
        if pos == target:
            return True
        for dev in pos:
            if not self._devices[dev].isAtTarget(pos[dev], target[dev]):
                return False
        return True

    def _mapReadValue(self, value):
        for val in self.mapping:
            if self._is_at_target(value, self.mapping[val]):
                return val
        if self.fallback is not None:
            return self.fallback
        else:
            raise PositionError(self, 'unknown unmapped position %r' % value)

    def _readRaw(self, maxage=0):
        return {dev.name: dev.read(maxage) for dev in self._devices.values()}

    def _startRaw(self, target):
        ramp = 60 * self.mapping[self.target]['ramp']
        seq = self._generateSequence(self.target)
        if self.target in ['off', 'safe']:
            seq.reverse()
        self._startSequence([
            SeqParam(dev, 'ramp', ramp)
            for dev in self._attached_anodes + self._attached_banodes +
            self._attached_cathodes + self._attached_window
        ] + seq)
Пример #10
0
class HasWindowTimeout(HasPrecision, HasTimeout):
    """
    Mixin class for devices needing a more fancy timeout handling than
    `HasTimeout`.

    Basically we keep a (length limited) history of past values and check if
    they are close enough to the target (deviation is smaller than
    `precision`). The length of that history is determined by
    :attr:`~HasWindowTimeout.window`.
    In any case the last read value is used to determine `isAtTarget`.
    If the value is outside the defined window for longer than
    :attr:`~HasTimeout.timeout` seconds after the HW is no longer busy.
    Also we add a stabilising phase in the timeouttimes list.
    """
    parameters = {
        'window':
        Param('Time window for checking stabilization',
              unit='s',
              default=60.0,
              fmtstr='%.1f',
              settable=True,
              category='general'),
    }

    parameter_overrides = {
        'precision': Override(mandatory=True, type=floatrange(1e-8)),
    }

    @lazy_property
    def _history(self):
        if self._cache:
            self._cache.addCallback(self, 'value', self._cacheCB)
            self._subscriptions.append(('value', self._cacheCB))
            t = currenttime()
            return self._cache.history(self, 'value', t - self.window, t)
        return []

    # use values determined by poller or waitForCompletion loop
    # to fill our history
    def _cacheCB(self, key, value, time):
        self._history.append((time, value))
        # clean out stale values, if more than one
        stale = None
        for i, entry in enumerate(self._history):
            t, _ = entry
            if t >= time - self.window:
                stale = i
                break
        else:
            return
        # remove oldest entries, but keep one stale
        if stale > 1:
            del self._history[:stale - 1]

    def _getTimeoutTimes(self, current_pos, target_pos, current_time):
        """Calculates timestamps for timeouts

        returns an iterable of tuples (status_string, timestamp) with ascending
        timestamps.
        First timestamp has to be `current_time` which is the only argument to
        this.
        The last timestamp will be used as the final timestamp to determine if
        the device's movement timed out or not.
        Additional timestamps (in between) may be set if need for
        _combinedStatus returning individual status text's (e.g. to
        differentiate between 'ramping' and 'stabilization').
        """
        res = HasTimeout._getTimeoutTimes(self, current_pos, target_pos,
                                          current_time)
        if not res:
            return None
        # we just append the window time after the timeout time
        # logically wrong order, but nobody uses the strings anyway
        res.append(('', res[-1][1] + self.window))
        return res

    def isAtTarget(self, val):
        ct = currenttime()
        self._cacheCB('value', val, ct)
        if self.target is None:
            return True

        # check subset of _history which is in window
        # also check if there is at least one value before window
        # to know we have enough datapoints
        hist = self._history[:]
        window_start = ct - self.window
        hist_in_window = [v for (t, v) in hist if t >= window_start]
        stable = all(
            abs(v - self.target) <= self.precision for v in hist_in_window)
        if 0 < len(hist_in_window) < len(hist) and stable:  # pylint: disable=len-as-condition
            if hasattr(self, 'doIsAtTarget'):
                return self.doIsAtTarget(val)
            return True
        return False

    def doTime(self, old, new):
        if old is None or new is None or old == new:
            return 0.
        if 'speed' in self.parameters and self.speed != 0:
            return abs(new - old) / self.speed + self.window
        elif 'ramp' in self.parameters and self.ramp != 0:
            return abs(new - old) / (self.ramp / 60.) + self.window
        return self.window

    def doEstimateTime(self, elapsed):
        if self.status()[0] != status.BUSY:
            return None
        if self.target is None:
            return None
        if 'setpoint' not in self.parameters or self.setpoint == self.target:
            # ramp finished, look at history to estimate from last point
            # outside
            now = currenttime()
            for t, v in reversed(list(self._history)):
                if abs(v - self.target) > self.precision:
                    return max(0, t + self.window - now + 1)
                if t < now - self.window:
                    break
            return 0.0
        # ramp unfinished, estimate ramp + window
        return self.doTime(self.read(), self.target)

    def _targetReached(self):
        """Do not call `_clearTimeout` as supposed by `HasTimeout` in order
Пример #11
0
class MicrostepMotor(BaseSequencer, NicosMotor):
    """Virtual motor which implements software based micro stepping for an
    attached Moveable device.
    """

    STATUS_TIME = .3  # waitForCompletion 300 ms
    CODE_TIME = .05  # additional code execution time in polling routine

    attached_devices = {
        "motor": Attach("Motor device which will be moved.", Moveable),
    }

    parameters = {
        "microstep":
        Param("Software/Pseudo micro step size",
              type=float,
              settable=True,
              mandatory=False,
              default=0.01),
        "maxtime":
        Param("Maximum time for one micro step in seconds",
              type=float,
              settable=True,
              mandatory=False,
              default=0.8),
        "maxspeed":
        Param("Maximum speed", type=float, settable=False, mandatory=False),
    }

    parameter_overrides = {"abslimits": Override(mandatory=False)}

    def doInit(self, mode):
        self._setROParam("maxspeed", self.microstep / self.maxmovetime)
        self._delay = self.maxtime
        if self.speed < 1e-8:
            self.log.warning("Speed has not been set. Set maximum speed %.4f",
                             self.maxspeed)
            self.speed = self.maxspeed

    @property
    def motor(self):
        return self._attached_motor

    def _maxmovetime(self, t):
        # maximum movement time including polling time plus additional code
        # execution time.
        return t + MicrostepMotor.STATUS_TIME + MicrostepMotor.CODE_TIME

    @property
    def maxmovetime(self):
        return self._maxmovetime(self.maxtime)

    def doReadAbslimits(self):
        return self._attached_motor.abslimits

    def doWriteAbslimits(self, value):
        self._attached_motor.abslimits = value

    def doReadUserlimits(self):
        return self._attached_motor.userlimits

    def doWriteUserlimits(self, value):
        self._attached_motor.userlimits = value

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

    def doWriteUnit(self, value):
        self._attached_motor.unit = value

    def doWriteSpeed(self, value):
        delay = self.microstep / value
        self.log.debug("""
   delay: %.4f
maxspeed: %.4f
 maxtime: %.4f""", delay, self.maxspeed, self.maxtime)
        if value < self.maxspeed or math.fabs(self.maxspeed - value) < 1e-5:
            self._delay = delay
        else:
            raise LimitError(
                self, "Speed too high. Maximum speed is %.4f" % self.maxspeed)

    def doWriteMaxtime(self, value):
        self._setROParam("maxspeed", self.microstep / self._maxmovetime(value))
        if self.speed > self.maxspeed:
            self.log.warning("Speed too high. Set speed to %.4f",
                             self.maxspeed)
            self.speed = self.maxspeed

    def doWriteMicrostep(self, value):
        self._setROParam("maxspeed", value / self.maxmovetime)
        if self.speed > self.maxspeed:
            self.log.warning("Speed too high. Set speed to %.4f",
                             self.maxspeed)
            self.speed = self.maxspeed

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

    def _generateSequence(self, target):
        pos = self.read(0)
        s = self.microstep if (target - pos) >= 0 else -self.microstep
        n = int((target - pos) / s)
        # handle floating point overflows
        # check whether or not one last microstep fits into movement to target.
        if (math.fabs((pos + (n + 1) * s) - target) < math.fabs(
                self.microstep / 10)):
            n += 1
        res = [(SeqDev(self._attached_motor,
                       pos + i * s), SeqSleep(self._delay))
               for i in range(1, n)]
        res.append((SeqDev(self._attached_motor,
                           target), SeqSleep(self._delay)))
        return res

    def _sequence(self, sequence):
        t = time.time()
        res = BaseSequencer._sequence(self, sequence)
        t = time.time() - t
        self.log.info("Movement finished, time elapsed %.4f.", t)
        return res

    def doStop(self):
        if not self._seq_is_running():
            self._stopAction(-1)
        BaseSequencer.doStop(self)

    def _stopAction(self, _nr):
        self._attached_motor.stop()
Пример #12
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)
Пример #13
0
class ImageChannel(QMesyDAQImage, BaseImageChannel):

    parameters = {
        'readout':
        Param('Readout mode of the Detector',
              settable=True,
              type=oneof('raw', 'mapped', 'amplitude'),
              default='mapped',
              mandatory=False,
              chatty=True),
        'flipaxes':
        Param('Flip data along these axes after reading from det',
              type=listof(int),
              default=[],
              unit=''),
        'transpose':
        Param('Whether to transpose the image', type=bool, default=False),
    }

    # Use the configuration from QMesyDAQ
    parameter_overrides = {
        'listmode': Override(volatile=True),
        'histogram': Override(volatile=True),
    }

    def doWriteListmode(self, value):
        self._dev.SetProperties(['writelistmode', ('%s' % value).lower()])
        return self.doReadListmode()

    def doReadListmode(self):
        return {
            'false': False,
            'true': True
        }[self._getProperty('writelistmode')]

    def doWriteHistogram(self, value):
        self._dev.SetProperties(['writehistogram', ('%s' % value).lower()])
        return self.doReadHistogram()

    def doReadHistogram(self):
        return {
            'false': False,
            'true': True
        }[self._getProperty('writehistogram')]

    def doWriteReadout(self, value):
        self._dev.SetProperties(['histogram', value])
        return self._getProperty('histogram')

    def doWriteListmodefile(self, value):
        self._dev.SetProperties(['lastlistfile', value])
        return self._getProperty('lastlistfile')

#   def doReadListmodefile(self):
#       return self._getProperty('lastlistfile')

    def doWriteHistogramfile(self, value):
        self._dev.SetProperties(['lasthistfile', value])
        return self._getProperty('lasthistfile')


#   def doReadHistogramfile(self):
#       return self._getProperty('lasthistfile')

    def doReadConfigfile(self):
        return self._getProperty('configfile')

    def doReadCalibrationfile(self):
        return self._getProperty('calibrationfile')

    def doReadArray(self, quality):
        narray = BaseImageChannel.doReadArray(self, quality)
        if self.transpose:
            narray = np.transpose(narray)
        for axis in self.flipaxes:
            narray = np.flip(narray, axis)
        return narray

    def doFinish(self):
        self.doStatus(0)
        return BaseImageChannel.doFinish(self)

    def doStop(self):
        self.doStatus(0)
        return BaseImageChannel.doStop(self)
Пример #14
0
class DataSink(Device):
    """The base class for data sinks.

    Each sink represents one way of processing incoming data.

    This is a device to be instantiated once per setup so that it can be
    configured easily through NICOS setups.  Actual processing is done by a
    `DataSinkHandler` class, of which one or more instances are created for
    each dataset that is processed with this sink.

    Usually, sinks are specific to a certain type of dataset (e.g. points or
    scans) and therefore override the `settypes` parameter with a default value
    that reflects this.

    .. attribute:: handlerclass

       This class attribute must be set by subclasses.  It selects the subclass
       of `.DataSinkHandler` that is to be used for handling datasets with this
       sink.

    .. attribute:: activeInSimulation

       This is a class attribute that selects whether this sink can be used in
       simulation mode.  This should only be true for sinks that write no data,
       such as a "write scan data to the console" sink.

    .. automethod:: isActive
    """

    parameters = {
        'detectors': Param('List of detector names to activate this sink '
                           '(default is always activated)', type=listof(str)),
        'settypes':  Param('List of dataset types to activate this sink '
                           '(default is for all settypes the sink supports)',
                           type=setof(*SETTYPES)),
    }

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

    # Set to true in subclasses that are safe for simulation.
    activeInSimulation = False

    # Set this to the corresponding Handler class.
    handlerclass = None

    def isActive(self, dataset):
        """Should return True if the sink can and should process this dataset.

        The default implementation, which should always be called in overrides,
        checks for simulation mode and for a match with the settypes and the
        detectors selected by the respective parameters.

        Derived classes can add additional checks, such as the dataset
        producing an array that can be written to an image file.
        """
        if session.mode == SIMULATION and not self.activeInSimulation:
            return False
        if self.settypes and dataset.settype not in self.settypes:
            return False
        if self.detectors and \
           not ({d.name for d in dataset.detectors} & set(self.detectors)):
            return False
        return True

    def createHandlers(self, dataset):
        """Start processing the given dataset (a BaseDataset).

        Creates the corresponding DataSinkHandler instances to use for this
        dataset determined via `handlerclass` and returns them.
        """
        if self.handlerclass is None:
            raise NotImplementedError('Must set an "handlerclass" attribute '
                                      'on %s' % self.__class__)
        # pylint: disable=not-callable
        if dataset.settype == POINT:
            dets = {d.name for d in dataset.detectors}
            if self.detectors:
                dets &= set(self.detectors)
            return [self.handlerclass(self, dataset, session.getDevice(det))
                    for det in dets]
        else:
            return [self.handlerclass(self, dataset, None)]
Пример #15
0
class DetAngle(HasLimits, Moveable):
    """Angle of the outgoing (centered) beam to detector."""

    attached_devices = {
        'tubeangle': Attach('Angle of the tube to the ground', Moveable),
        'tubepos': Attach('Position of detector inside tube', Readable),
        'pivot': Attach('Position of the pivot point', Readable),
        'theta': Attach('Tilt angle of the sample', Readable, optional=True),
    }

    parameters = {
        'pivot1pos':
        Param('Distance of the pivot point 1 from wall',
              type=floatrange(0),
              mandatory=False,
              default=290,
              unit='mm'),
        'b2pos':
        Param('Distance of B2 aperture from wall',
              type=floatrange(0),
              mandatory=False,
              default=165,
              unit='mm'),
        'b3pos':
        Param('Distance of B3 aperture from wall',
              type=floatrange(0),
              mandatory=False,
              default=285,
              unit='mm'),
        'samplepos':
        Param('Distance of sample from B3',
              type=floatrange(0),
              mandatory=False,
              default=50,
              unit='mm'),
        'detheight':
        Param('Height of the detector',
              type=floatrange(0),
              mandatory=False,
              default=533.715,
              unit='mm'),
        # calculated from the beamheight - pivot.height - (256 - 160) * 2.093 mm
        # Beam height at 0 deg is in pixel 160 counted from top pixel (256)
        # times pixel size (2.093 mm)
        'detoffset':
        Param('Offset from virtual tube base to lower edge of '
              'detector',
              type=floatrange(0),
              mandatory=False,
              default=619.772,
              unit='mm'),
        'beamheight':
        Param('Height of beam above ground level',
              type=floatrange(0),
              mandatory=False,
              default=1193.7,
              unit='mm'),
        'range':
        Param('range of angles between upper and lower detector edge',
              type=tupleof(float, float),
              volatile=True,
              unit='deg, deg',
              internal=True,
              preinit=False),
    }

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

    def doPreinit(self, mode):
        # center of the detector in respect to the tube base line
        self._detcenter = self.detheight / 2 + self.detoffset
        self._yoffset = self.beamheight - self._attached_pivot.height

    def doInit(self, mode):
        self._func = np.vectorize(self._alpha)

    def doRead(self, maxage=0):
        self._update(maxage)
        beta = self._attached_tubeangle.read(maxage)
        self.log.debug('Tube angle: %.3f', beta)
        alphaf = self._alpha(beta)
        if self._attached_theta:
            alphaf -= self._attached_theta.read(maxage)
        return alphaf

    def _update(self, maxage=0):
        pivot = self._attached_pivot
        # Calculate the distance between sample and pivot point
        self._xoffset = self.pivot1pos + pivot.grid * (
            pivot.read(maxage) - 1) - (self.samplepos + self.b3pos)
        self.log.debug('Sample pivot distance : %.1f', self._xoffset)
        self._tpos = self._attached_tubepos.read(maxage)

    def _alpha(self, beta):
        beta = radians(beta)
        # calculate the detector center position in respect to sample
        x = self._xoffset + self._tpos * cos(beta) - self._detcenter * sin(
            beta)

        # calculate the height of the detector center in respect to the ground
        y = self._tpos * sin(beta) + self._detcenter * cos(
            beta) - self._yoffset
        return degrees(atan2(y, x))

    def doStart(self, target):
        self._update(0)
        x = np.arange(self.absmin, self.absmax + 0.01, 0.01)
        y = self._func(x)
        if self._attached_theta:
            target += self._attached_theta.read(0)
        val = np.interp(target, y, x)
        self.log.debug('new beta: %f', val)
        self._attached_tubeangle.start(val)

    def doReadAbslimits(self):
        return self._attached_tubeangle.abslimits

    def doReadRange(self):
        alpha = self.doRead(0)
        opening = degrees(atan2(self.detheight, self._tpos)) / 2.
        return (alpha - opening, alpha + opening)
Пример #16
0
class ImagePlateDrum(PyTangoDevice, Moveable):
    """ImagePlateDrum implements erasing, moving to expo position and readout
    for MAATEL Image Plate Detectors.
    """

    DEFAULT_URL_FMT = "tango://%s/EMBL/Microdiff/General#dbase=no"

    tango_status_mapping = dict(PyTangoDevice.tango_status_mapping)
    tango_status_mapping[DevState.STANDBY] = status.OK
    tango_status_mapping[DevState.ALARM] = status.ERROR

    POS_ERASE = "erase"
    POS_EXPO = "expo"
    POS_READ = "read"

    valuetype = oneof(POS_ERASE, POS_EXPO, POS_READ)

    parameters = {
        "drumpos": Param("Drum position in degree", type=float,
                         settable=True, volatile=True, category="general"),
        "readheadpos": Param("Read head motor position in mm",
                             type=float, settable=True, volatile=True,
                             category="general"),
        "drumexpo": Param("Drum expo position in degree",
                          type=float, settable=True, volatile=True,
                          category="general"),
        "readspeed": Param("Readout velocity for the detector drum "
                           "in rpm", type=float, settable=True,
                           volatile=True, category="general"),
        "erasespeed": Param("Erase velocity for the detector drum "
                            "in rpm", type=float, settable=True,
                            volatile=True, category="general"),
        "freqlaser": Param("Frequency for the laser diode in Hz",
                           type=float, settable=True, volatile=True,
                           category="general"),
        "timeerase": Param("Erasure time in seconds", type=float,
                           settable=True, volatile=True, category="general"),
    }

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

    def doInit(self, mode):
        self._lastStatus = None
        self._moveTo = None
        if mode == SIMULATION:
            self._mapStart = {}
            self._mapStop = {}
            return
        self._mapStart = {
            ImagePlateDrum.POS_ERASE: self._dev.StartErasureProcess,
            ImagePlateDrum.POS_EXPO: self._dev.MoveExpoPosition,
            ImagePlateDrum.POS_READ: self._dev.StartReadProcess,
        }
        self._mapStop = {
            ImagePlateDrum.POS_ERASE: self._dev.AbortErasureProcess,
            ImagePlateDrum.POS_EXPO: self._dev.AbortExposureProcess,
            ImagePlateDrum.POS_READ: self._dev.AbortReadProcess,
        }

    def doStart(self, pos):
        self.log.debug("doStart: pos: %s", pos)
        myStatus = self.status(0)
        if myStatus[0] == status.OK:
            self._moveTo = pos
            self._mapStart[pos]()
        else:
            raise MoveError(self, "Movement not allowed during device status "
                            "'%s'" % (status.statuses[myStatus[0]]))

    def doStop(self):
        self.log.debug("doStop")
        if self._moveTo in self._mapStop:
            self._mapStop[self._moveTo]()
        else:
            myStatus = self.status(0)
            if myStatus[0] == status.OK:
                self.log.warning("Device already stopped.")
            else:
                raise NicosError(self, "Internal moveTo state unknown. "
                                       "Check device status.")

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

    def doStatus(self, maxage=0):
        # Workaround for status changes from busy to another state although the
        # operation has _not_ been completed.
        st, msg = PyTangoDevice.doStatus(self, maxage)
        if self._lastStatus == status.BUSY and st != status.BUSY:
            self.log.debug("doStatus: leaving busy state (%d)? %d. "
                           "Check again after a short delay.", status.BUSY, st)
            session.delay(5)
            st, msg = PyTangoDevice.doStatus(self, 0)
            self.log.debug("doStatus: recheck result: %d", st)
        self._lastStatus = st
        return st, msg

    def doFinish(self):
        self._moveTo = None

    def doReadDrumpos(self):
        return self._dev.DrumPosition

    def doReadReadheadpos(self):
        return self._dev.ReadHeadPosition

    def doReadDrumexpo(self):
        return self._dev.DrumExpoPosition

    def doReadReadspeed(self):
        return self._dev.ReadingDrumJogSpeed

    def doReadErasespeed(self):
        return self._dev.ErasureDrumJogSpeed

    def doReadFreqlaser(self):
        return self._dev.LaserDiodeLevel

    def doReadTimeerase(self):
        return self._dev.ErasureDuration

    def doWriteDrumpos(self, value):
        self._dev.DrumPosition = value

    def doWriteReadheadpos(self, value):
        self._dev.ReadHeadPosition = value

    def doWriteDrumexpo(self, value):
        self._dev.DrumExpoPosition = value

    def doWriteReadspeed(self, value):
        self._dev.ReadingDrumJogSpeed = value

    def doWriteErasespeed(self, value):
        self._dev.ErasureDrumJogSpeed = value

    def doWriteFreqlaser(self, value):
        self._dev.LaserDiodeLevel = value

    def doWriteTimeerase(self, value):
        self._dev.ErasureDuration = value
Пример #17
0
class Changer(BaseSequencer):
    attached_devices = {
        'lift':
        Attach('Lift moving a mono up or down between 4 '
               'positions', Moveable),
        'magazine':
        Attach('Magazine holding the monos and having 4 '
               'positions', Moveable),
        'liftclamp':
        Attach('Clamp holding the mono in the lift', Moveable),
        'magazineclamp':
        Attach('Clamp holding the mono in the magazine', Moveable),
        'tableclamp':
        Attach('Clamp holding the mono on the table', Moveable),
        'inhibitrelay':
        Attach('Inhibit to the remaining motor controllers', Moveable),
        'enable':
        Attach('To enable operation of the changer', Moveable),
        'magazineocc':
        Attach('Readout for occupancy of the magazine', Readable),
        'magazinestatus':
        Attach('Readout for status of magazine load position', Readable),
    }

    parameters = {
        'mono_on_table':
        Param('Name of Mono on the Monotable',
              type=oneof('PG', 'Si', 'Cu', 'Heusler', 'None', 'empty frame'),
              default='None',
              settable=True,
              internal=True),
        'mono_in_lift':
        Param('Which mono is in the lift',
              type=oneof('PG', 'Si', 'Cu', 'Heusler', 'None', 'empty frame'),
              default='None',
              settable=True,
              internal=True),
        'exchangepos':
        Param(
            'dict of device names to positional values '
            'for changing monos',
            type=dict,
            settable=False,
            userparam=False),
        'precisionchange':
        Param(
            'dict of device names and pairs of (normal '
            'value, change value) of the precision',
            type=dict,
            settable=False,
            userparam=False),
    }

    parameter_overrides = {
        'requires': Override(default={'level': 'admin'}),
    }

    positions = ['101', '110', '011', '111']  # CounterClockwise
    monos = ['Heusler', 'PG', 'Si', 'empty frame']  # assigned monos
    shields = ['110', '110', '110', '110']  # which magzinslot after changing
    #    (e.g. Put a PE dummy to 101 and set this to ('101,'*4).split(',')
    valuetype = oneof(*(monos + ['None']))

    def PrepareChange(self):
        '''is checking whether all the conditions for a change of mono are met'''

        # if not(self.SecShutter_is_closed()):
        #     raise UsageError(self, 'Secondary Shutter needs to be closed, '
        #                      'please close by hand and retry!')
        # if self.NotAus():
        #     raise UsageError(self, 'NotAus (Emergency stop) activated, '
        #                      'please check and retry!')
        # read all inputs and check for problems
        if not (self._attached_magazine.read() in self.positions):
            raise HWError(self, 'Unknown Magazine-Position !')
        if self._attached_lift.read() != '2':
            raise HWError(self, 'Lift not at parking position!')
        if self._attached_liftclamp.read() != 'close':
            raise HWError(self, 'liftclamp should be closed!')
        if self._attached_tableclamp.read() != 'close':
            raise HWError(self, 'tableclamp should be closed!')
        if self._attached_magazineclamp.read() != 'close':
            raise HWError(self, 'magazineclamp should be closed!')
        if self.mono_in_lift != 'None':
            raise HWError(
                self, 'There is mono %s in the lift, please change '
                'manually!' % self.mono_in_lift)

        # XXX TODO: store old values of positions and offsets someplace to
        # recover after changing back
        self.log.info('will change position of several devices to the '
                      'changing position, moving back is not yet implemented')

        # enhance precision of the devices
        for devname, prec in self.precisionchange.items():
            dev = session.getDevice(devname)
            dev.precision = prec[1]
            self.log.debug('changing precision of the %s device to the %f',
                           devname, prec[1])

        # go to the place where a change is possible
        devs = []
        for devname, pos in self.exchangepos.items():
            dev = session.getDevice(devname)
            # remove after implementation of moving back
            self.log.info('position of the %s device was %f', devname, dev())
            if isinstance(dev, HasOffset):
                dev.start(pos - dev.offset)
                self.log.debug(
                    'move and wait %s to the position %f - offset of %f',
                    devname, pos, dev.offset)
                dev.wait()
            else:
                dev.start(pos)
                self.log.debug('move and wait %s to the position %f', devname,
                               pos)
                dev.wait()
            devs.append(dev)
        multiWait(devs)

        # put precision back to normal
        for devname, prec in self.precisionchange.items():
            dev = session.getDevice(devname)
            dev.precision = prec[0]
            self.log.debug(
                'changing precision of the %s device back to '
                'the %f', devname, prec[0])

        try:
            dev = session.getDevice('focibox')
            self.mono_on_table = dev.read(0)
            dev.comm('XMA', forcechannel=False)
            dev.comm('YMA', forcechannel=False)
            dev.driverenable = False
            self.log.info('focus motors disabled')
        except NicosError as err:
            self.log.error('Problem disabling foci: %s', err)

        # switch on inhibit and enable
        self._attached_enable.maw(0xef16)
        self._attached_inhibitrelay.maw('on')
        self.log.info('changer enabled and inhibit active')

    def FinishChange(self):
        self._attached_inhibitrelay.maw('off')
        self._attached_enable.maw(0)
        self.log.warning('Please restart the daemon or reload the setups to '
                         'init the new Mono:')
        self.log.warning('  > NewSetup()')
        self.log.info('  > NewSetup()')

    def CheckMagazinSlotEmpty(self, slot):
        # checks if the given slot in the magazin is empty
        self.log.info('checking of there IS NOT mono in magazine slot %r' %
                      slot)
        if self._attached_magazineocc.status(0)[0] != status.OK:
            raise UsageError(
                self, 'Magazine occupancy switches are in warning state!')
        index = self.positions.index(slot)
        if not ((self._attached_magazineocc.read() >> index * 2) & 1):
            raise UsageError(self, 'Position %r is already occupied!' % slot)

    def CheckMagazinSlotUsed(self, slot):
        # checks if the given slot in the magazin is used (contains a monoframe)
        self.log.info('checking of there IS mono in magazine slot %r' % slot)
        if self._attached_magazineocc.status(0)[0] != status.OK:
            raise UsageError(
                self, 'Magazine occupancy switches are in warning state!')
        index = self.positions.index(slot)
        if (self._attached_magazineocc.read() >> index * 2) & 1:
            raise UsageError(self, 'Position %r is empty!' % slot)

    def _start(self, seq):
        if self._seq_is_running():
            raise MoveError(self, 'Can not start sequence, device is still '
                            'busy')
        self._startSequence(seq)

    # here is the party going on!
    def Transfer_Mono_Magazin2Lift(self):
        self._start(self._gen_mag2lift())
        self.wait()

    def _gen_mag2lift(self, magpos=None):
        seq = []
        if magpos is None:
            magpos = self._attached_magazine.read(0)
        else:
            seq.append(SeqDev(self._attached_magazine, magpos))
        # check preconditions
        seq.append(
            SeqCall(self.log.info, 'checking preconditions for Magazin2Lift'))
        seq.append(SeqCheckStatus(self._attached_magazine, status.OK))
        seq.append(SeqCheckStatus(self._attached_lift, status.OK))
        seq.append(SeqCheckPosition(self._attached_lift, '2'))
        seq.append(SeqCheckPosition(self._attached_liftclamp, 'close'))
        seq.append(SeqCheckPosition(self._attached_magazine, magpos))
        seq.append(SeqCheckPosition(self._attached_magazineclamp, 'close'))
        seq.append(SeqMethod(self, 'CheckMagazinSlotUsed', magpos))
        seq.append(SeqCheckAttr(self, 'mono_in_lift', 'None'))
        # transfer mono to lift
        seq.append(
            SeqCall(self.log.info, 'transferring mono from magazine to lift'))
        seq.append(SeqDev(self._attached_liftclamp, 'open'))
        seq.append(SeqDev(self._attached_lift, '3'))  # almost top position
        seq.append(SeqMethod(self._attached_liftclamp, 'start', 'close'))
        seq.append(SeqDev(self._attached_lift, '4'))  # top position
        seq.append(SeqDev(self._attached_liftclamp, 'close'))
        seq.append(SeqMethod(self._attached_magazineclamp, 'start', 'open'))
        # rattle a little
        seq.append(SeqCall(self.log.info, 'rattle to release magazine grab'))
        seq.append(SeqDev(self._attached_lift, '3'))  # almost top position
        seq.append(SeqDev(self._attached_lift, '4'))  # top position
        seq.append(SeqDev(self._attached_lift, '3'))  # almost top position
        seq.append(SeqDev(self._attached_lift, '4'))  # top position
        seq.append(SeqDev(self._attached_magazineclamp, 'open'))
        seq.append(SeqMethod(self, 'CheckMagazinSlotEmpty', magpos))
        # move (with mono) to parking position
        seq.append(
            SeqCall(self.log.info, 'moving with mono to parking position'))
        seq.append(
            SeqSetAttr(self, 'mono_in_lift',
                       self.monos[self.positions.index(magpos)]))
        seq.append(SeqDev(self._attached_lift, '2'))  # park position
        seq.append(SeqDev(self._attached_magazineclamp, 'close'))
        # Magazin should not contain a mono now
        seq.append(SeqMethod(self, 'CheckMagazinSlotEmpty', magpos))
        return seq

    def Transfer_Mono_Lift2Magazin(self):
        self._start(self._gen_lift2mag())
        self.wait()

    def _gen_lift2mag(self, magpos=None):
        seq = []
        if magpos is None:
            magpos = self._attached_magazine.read(0)
        else:
            seq.append(SeqDev(self._attached_magazine, magpos))
        # check preconditions
        seq.append(
            SeqCall(self.log.info, 'checking preconditions for Lift2Magazin'))
        seq.append(SeqCheckStatus(self._attached_magazine, status.OK))
        seq.append(SeqCheckStatus(self._attached_lift, status.OK))
        seq.append(SeqCheckPosition(self._attached_lift, '2'))
        seq.append(SeqCheckPosition(self._attached_liftclamp, 'close'))
        seq.append(SeqCheckPosition(self._attached_magazine, magpos))
        seq.append(SeqCheckPosition(self._attached_magazineclamp, 'close'))
        seq.append(SeqMethod(self, 'CheckMagazinSlotEmpty', magpos))
        # there needs to be a mono in the lift
        seq.append(
            SeqCall(self.log.info, 'checking if there is a mono in lift'))
        seq.append(
            SeqCheckAttr(self,
                         'mono_in_lift',
                         values=[m for m in self.monos if m != 'None']))
        # prepare magazin
        seq.append(SeqCall(self.log.info, 'testing magazin grab'))
        seq.append(SeqDev(self._attached_magazineclamp, 'open'))
        seq.append(SeqDev(self._attached_magazineclamp, 'close'))
        seq.append(SeqDev(self._attached_magazineclamp, 'open'))
        seq.append(SeqDev(self._attached_magazineclamp, 'close'))
        seq.append(SeqDev(self._attached_magazineclamp, 'open'))
        # transfer mono to lift
        seq.append(SeqCall(self.log.info, 'moving lift to top position'))
        seq.append(SeqDev(self._attached_lift, '4'))  # top position
        seq.append(
            SeqCall(self.log.info,
                    'closing the magazin grab and rattling lift'))
        seq.append(SeqMethod(self._attached_magazineclamp, 'start', 'close'))
        # rattle a little
        seq.append(SeqDev(self._attached_lift, '3'))  # almost top position
        seq.append(SeqDev(self._attached_lift, '4'))  # top position
        seq.append(SeqDev(self._attached_lift, '3'))  # almost top position
        seq.append(SeqDev(self._attached_lift, '4'))  # top position
        seq.append(SeqDev(self._attached_lift, '3'))  # almost top position
        seq.append(SeqDev(self._attached_magazineclamp, 'close'))
        seq.append(SeqMethod(self, 'CheckMagazinSlotUsed', magpos))
        seq.append(SeqCall(self.log.info, 'opening lift clamp'))
        seq.append(SeqMethod(self._attached_liftclamp, 'start', 'open'))
        seq.append(SeqDev(self._attached_lift, '4'))  # top position
        seq.append(SeqDev(self._attached_lift, '3'))  # top position
        seq.append(SeqDev(self._attached_liftclamp, 'open'))
        seq.append(SeqCall(self.log.info, 'moving lift to park position'))
        seq.append(SeqDev(self._attached_lift, '2'))  # park position
        seq.append(SeqCall(self.log.info, 'closing lift clamp'))
        seq.append(SeqDev(self._attached_liftclamp, 'close'))
        # move (without mono) to parking position
        seq.append(SeqSetAttr(self, 'mono_in_lift', 'None'))
        # Magazin should not contain a mono now
        seq.append(SeqMethod(self, 'CheckMagazinSlotUsed', magpos))
        return seq

    def Transfer_Mono_Lift2Table(self):
        self._start(self._gen_lift2table())
        self.wait()

    def _gen_lift2table(self):
        seq = []
        # check preconditions
        seq.append(
            SeqCall(self.log.info, 'checking preconditions for Lift2Table'))
        seq.append(SeqCheckStatus(self._attached_tableclamp, status.OK))
        seq.append(SeqCheckStatus(self._attached_liftclamp, status.OK))
        seq.append(SeqCheckStatus(self._attached_lift, status.OK))
        seq.append(SeqCheckPosition(self._attached_lift, '2'))
        seq.append(SeqCheckPosition(self._attached_liftclamp, 'close'))
        seq.append(SeqCheckPosition(self._attached_tableclamp, 'close'))
        # there shall be a mono in the lift!
        seq.append(SeqCall(self.log.info, 'check if there is a mono in Lift'))
        seq.append(
            SeqCheckAttr(self,
                         'mono_in_lift',
                         values=[m for m in self.monos if m != 'None']))
        seq.append(SeqCheckAttr(self, 'mono_on_table', 'None'))
        seq.append(SeqCall(self.log.info, 'moving down the lift'))
        # transfer mono to table
        seq.append(SeqDev(self._attached_tableclamp, 'open'))
        seq.append(SeqDev(self._attached_lift, '1'))  # bottom position
        seq.append(
            SeqCall(self.log.info,
                    'closing table grab and releasing lift clamp'))
        seq.append(SeqDev(self._attached_tableclamp, 'close'))
        # move (without mono) to parking position
        seq.append(SeqDev(self._attached_liftclamp, 'open'))

        def func(self):
            self.mono_on_table, self.mono_in_lift = self.mono_in_lift, 'None'

        seq.append(SeqCall(func, self))
        # seq.append(SeqSetAttr(self, 'mono_on_table', self.mono_in_lift))
        # seq.append(SeqSetAttr(self, 'mono_in_lift', 'None'))
        seq.append(SeqCall(self.log.info, 'moving lift to park position'))
        seq.append(SeqDev(self._attached_lift, '2'))  # park position
        seq.append(SeqDev(self._attached_liftclamp, 'close'))
        # TODO: change foci alias and reactivate foci
        return seq

    def Transfer_Mono_Table2Lift(self):
        self._start(self._gen_table2lift())
        self.wait()

    def _gen_table2lift(self):
        # XXX TODO drive all foci to 0 and switch of motors....
        # XXX TODO move mty/mtx to monospecific abholposition
        # hier nur das reine abholen vom Monotisch
        seq = []
        # check preconditions
        seq.append(
            SeqCall(self.log.info, 'checking preconditions for Table2Lift'))
        seq.append(SeqCheckStatus(self._attached_tableclamp, status.OK))
        seq.append(SeqCheckStatus(self._attached_liftclamp, status.OK))
        seq.append(SeqCheckStatus(self._attached_lift, status.OK))
        seq.append(SeqCheckPosition(self._attached_lift, '2'))
        seq.append(SeqCheckPosition(self._attached_liftclamp, 'close'))
        seq.append(SeqCheckPosition(self._attached_tableclamp, 'close'))
        # there shall be no mono in the lift, but one on the table
        seq.append(
            SeqCall(self.log.info, 'checking if there IS NOT a mono in Lift'))
        seq.append(SeqCheckAttr(self, 'mono_in_lift', 'None'))
        seq.append(
            SeqCheckAttr(self,
                         'mono_on_table',
                         values=[m for m in self.monos if m != 'None']))
        # transfer mono to lift
        seq.append(SeqCall(self.log.info, 'moving down the lift'))
        seq.append(SeqDev(self._attached_liftclamp, 'open'))
        seq.append(SeqDev(self._attached_lift, '1'))  # bottom position
        seq.append(SeqCall(self.log.info, 'grabbing the monochromator'))
        seq.append(SeqDev(self._attached_liftclamp, 'close'))
        seq.append(SeqDev(self._attached_tableclamp, 'open'))

        # move (with mono) to parking position
        def func(self):
            self.mono_on_table, self.mono_in_lift = 'None', self.mono_on_table

        seq.append(SeqCall(func, self))
        # seq.append(SeqSetAttr(self, 'mono_on_table', 'None'))
        # seq.append(SeqSetAttr(self, 'mono_in_lift', self.mono_on_table))
        seq.append(
            SeqCall(self.log.info,
                    'moving the lift with mono to parking position'))
        seq.append(SeqDev(self._attached_lift, '2'))  # park position
        seq.append(SeqDev(self._attached_tableclamp, 'close'))
        return seq

    def doStart(self, target):
        self.change(self.mono_on_table, target)

    def change(self, old, whereto):
        ''' cool kurze Wechselroutine
        Der Knackpunkt ist in den Hilfsroutinen!'''
        if not (old in self.monos + ['None']):
            raise UsageError(
                self, '\'%s\' is illegal value for Mono, use one '
                'of ' % old + ', '.join(self.monos + ['None']))
        if not (whereto in self.monos + ['None']):
            raise UsageError(
                self, '\'%s\' is illegal value for Mono, use one '
                'of ' % whereto + ', '.join(self.monos + ['None']))
        self.PrepareChange()
        if self.monos.index(whereto) == self.monos.index(old):
            # Nothing to do, requested Mono is supposed to be on the table
            return
        # Ok, we have a good state, the only thing we do not know is which mono
        # is on the table......
        # for this we use the (cached) parameter mono_on_table

        if self.mono_on_table != old:
            raise UsageError(
                self, 'Mono %s is not supposed to be on the '
                'table, %s is!' % (old, self.mono_on_table))

        seq = []
        # 0) move magazine to mono position
        magpos_to_put = self.positions[self.monos.index(old)]
        seq.append(SeqMethod(self, 'CheckMagazinSlotEmpty', magpos_to_put))
        seq.append(SeqDev(self._attached_magazine, magpos_to_put))
        # 1) move away the old mono, if any
        if old != 'None':
            seq.extend(self._gen_table2lift())
            seq.extend(
                self._gen_lift2mag(self.positions[self.monos.index(old)]))
        # 2) fetch the new mono (if any) from the magazin
        if whereto != 'None':
            seq.extend(
                self._gen_mag2lift(self.positions[self.monos.index(whereto)]))
            seq.extend(self._gen_lift2table())
            seq.append(
                SeqDev(self._attached_magazine,
                       self.shields[self.monos.index(whereto)]))

        # seq.append(SeqDev(self._attached_enable, 0)) - will be done in FinishChange
        seq.append(SeqMethod(self, 'FinishChange'))
        self._start(seq)
        self.wait()

    @usermethod
    def printstatusinfo(self):
        self.log.info(
            'PLC is %s', 'enabled' if self._attached_enable.read() == 0xef16
            else 'disabled, you need to set enable_word to the '
            'correct value')
        self.log.info('Inhibit_relay is %s',
                      self._attached_inhibitrelay.read())
        liftposnames = {
            '0': 'unknown, error in switches',
            '1': 'Monotable loading',
            '2': 'Park position',
            '3': 'Bottom storage',
            '4': 'Upper storage'
        }
        self.log.info('lift is at %s',
                      liftposnames[self._attached_lift.read()])
        try:
            magpos = self._attached_magazine.read()
            self.log.info('magazine is at %r which is assigned to %s', magpos,
                          self.monos[self.positions.index(magpos)])
        except Exception:
            self.log.error('magazine is at an unknown position !!!')
        for n in 'liftclamp magazineclamp tableclamp'.split():
            self.log.info('%s is %s', n, self._adevs[n].read())
        occ = self._attached_magazineocc.read(0)
        for i in range(4):
            self.log.info(
                'magazine slot %r is %sempty and its readouts are %sbroken',
                self.positions[i], '' if (occ >> (i * 2)) & 1 else 'not ',
                '' if (occ >> (i * 2 + 1)) & 1 else 'not ')
        if self._attached_magazinestatus.read() == 'free':
            self.log.info('magazine is currently free to load monochromator')
        else:
            self.log.info('magazine is currently occupied with monochromator '
                          'and cannot load another')

    def doRead(self, maxage=0):
        return ''