Example #1
0
def test_oneofdict():
    assert oneofdict({'A': 1, 'B': 2})('A') == 1
    assert oneofdict({'A': 1, 'B': 2})(1) == 1
    assert oneofdict({})() is None
    assert raises(ValueError, oneofdict({'A': 1}), 2)

    assert none_or(int)(None) is None
    assert none_or(int)(5.0) == 5
    assert raises(ValueError, none_or(int), 'x')
Example #2
0
class NOKMonitoredVoltage(Sensor):
    """Return a scaled and monitored Analogue value.

    Also checks the value to be within certain limits, if not, complain.
    """

    parameters = {
        'reflimits':
        Param('None or Limits to check the reference: low, warn, '
              'high',
              type=none_or(tupleof(float, float, float)),
              settable=False,
              default=None),
        'scale':
        Param('Scaling factor', type=float, settable=False, default=1.),
    }
    parameter_overrides = {
        'unit': Override(default='V', mandatory=False),
    }

    def doInit(self, mode):
        if self.reflimits is not None:
            if not (0 <= self.reflimits[0] <= self.reflimits[1] <=
                    self.reflimits[2]):
                raise ConfigurationError(
                    self, 'reflimits must be in ascending'
                    ' order!')

    def doRead(self, maxage=0):
        value = self.scale * Sensor.doRead(self, maxage)
        if self.reflimits is not None:
            if abs(value) > self.reflimits[2]:
                raise HardwareError(
                    self, 'Reference voltage (%.2f) above '
                    'maximum (%.2f)' % (value, self.reflimits[2]))
            if abs(value) < self.reflimits[0]:
                raise HardwareError(
                    self, 'Reference voltage (%.2f) below '
                    'minimum (%.2f)' % (value, self.reflimits[0]))
            if abs(value) < self.reflimits[1]:
                self.log.warning(
                    'Reference voltage (%.2f) seems rather low, '
                    'should be above %.2f', value, self.reflimits[1])
        return value

    def doStatus(self, maxage=0):
        try:
            self.doRead(maxage)
            return Sensor.doStatus(self, maxage)
        except HardwareError as err:
            return status.ERROR, repr(err)
Example #3
0
class NOKMotorIPC(CanReference, IPCMotor):
    """Basically a IPCMotor with referencing."""

    parameters = {
        'refpos':
        Param('Reference position in phys. units',
              unit='main',
              type=none_or(float),
              mandatory=True),
    }

    parameter_overrides = {
        'zerosteps': Override(default=500000, mandatory=False),
        'unit': Override(default='mm', mandatory=False),
        'backlash': Override(type=floatrange(0.0, 0.0)),  # only 0 is allowed!
        'speed': Override(default=10),
        'accel': Override(default=10),
        'slope': Override(default=2000),
        'confbyte': Override(default=48),
        'divider': Override(type=intrange(-1, 7)),
    }

    def doInit(self, mode):
        if mode != SIMULATION:
            self._attached_bus.ping(self.addr)
            if self._hwtype == 'single':
                if self.confbyte != self.doReadConfbyte():
                    self.doWriteConfbyte(self.confbyte)
                    self.log.warning(
                        'Confbyte mismatch between setup and card'
                        ', overriding card value to 0x%02x', self.confbyte)
            # make sure that the card has the right "last steps"
            # This should not applied at REFSANS, since it disturbs the running
            # TACO server settings
            # if self.steps != self.doReadSteps():
            #     self.doWriteSteps(self.steps)
            #     self.log.warning('Resetting stepper position to last known '
            #                      'good value %d', self.steps)
            self._type = 'stepper motor, ' + self._hwtype
        else:
            self._type = 'simulated stepper'

    def doReference(self):
        bus = self._attached_bus
        bus.send(self.addr, 34)  # always go forward (positive)
        bus.send(self.addr, 47, self.speed, 3)  # reference with normal speed
        # may need to sleep a little here....
        session.delay(0.1)
        self.wait()
        self.doSetPosition(self.refpos)
Example #4
0
class FileSink(DataSink):
    """Base class for sinks that save data into files."""

    parameters = {
        'subdir':           Param('Filetype specific subdirectory name',
                                  type=subdir, mandatory=False, default=''),
        'filenametemplate': Param('List of templates for data file names '
                                  '(will be hardlinked), can contain '
                                  'subdirectories',
                                  ext_desc=TEMPLATE_DESC, type=listof(str),
                                  default=['%(pointcounter)08d.dat'],
                                  settable=False, prefercache=False),
        'filemode':         Param('File access rights after closing the file, '
                                  "if set to 'none' (default) the OS defaults "
                                  'will be used',
                                  type=none_or(intrange(0o000, 0o777),)),
    }
Example #5
0
class AndorHFRCamera(PyTangoDevice, UsesFastshutter, ImageChannelMixin,
                     ActiveChannel):
    """Camera communicating with Andor Basic script."""

    TRIGGER_MODES = {
        'internal': 0,
        'external': 1,
        'external start': 6,
        'external exposure': 7,
        'external exposure FVB': 9,
    }

    SHUTTER_MODES = {
        'rolling': 0,
        'global': 1,
    }

    parameters = {
        'bin':
        Param('Binning (x,y)',
              type=tupleof(intrange(1, 64), intrange(1, 64)),
              settable=True,
              default=(1, 1),
              category='general'),
        'triggermode':
        Param('Triggering signal definition',
              type=oneof(*TRIGGER_MODES),
              settable=True,
              default='internal',
              category='general'),
        'shuttermode':
        Param('Shutter mode setting',
              type=oneof(*SHUTTER_MODES),
              settable=True,
              default='rolling',
              category='general'),
        'spooldirectory':
        Param('Path to spool the images on the remote '
              'machine',
              type=absolute_win_path,
              category='general'),
        'extratime':
        Param('Extra sleeping time to sync with Andors Basic',
              type=floatrange(0),
              default=3,
              settable=True),
        '_started':
        Param('Cached counting start flag',
              type=none_or(float),
              default=None,
              settable=True,
              internal=True),
    }

    def doPreinit(self, mode):
        PyTangoDevice.doPreinit(self, mode)
        self.log.info('Checking if camera script is ready!')
        try:
            msg = self._dev.communicate('ready?')
            if msg.strip() != 'ready!':
                raise CommunicationError(
                    self, 'Camera script gave wrong '
                    'answer - please check!')
        except NicosError:
            self.log.warning('Camera is not responding - please start '
                             'tomography script on camera first!')
            raise

    def doReset(self):
        self.log.info('Checking if camera script is ready!')
        try:
            msg = self._dev.communicate('ready?')
            if msg.strip() != 'ready!':
                raise CommunicationError(
                    self, 'Camera script gave wrong '
                    'answer - please check!')
        except NicosError:
            self.log.warning('Camera is not responding - please start '
                             'tomography script on camera first!')
            raise

    def doInit(self, mode):
        self._started = None

    def valueInfo(self):
        # no readresult by default
        return ()

    def doStart(self):
        self.bin = self.bin
        self.shuttermode = self.shuttermode
        self.triggermode = self.triggermode
        self.doWriteSpooldirectory(self.spooldirectory)
        kct = float(self._query_value('GetKineticCycleTime'))
        self.log.info('kct: %s', kct)
        self._counttime = self._knumber * kct + 3
        self.log.info('measure time = %s s', self._counttime)
        self.openFastshutter()
        self._write_command('count')
        self._started = currenttime()
        self.log.debug('started: %s', self._started)

    def doSetPreset(self, **presets):
        for preset, val in presets.items():
            self._write_presets(preset, val)
        self._write_presets(
            'spoolfile',
            presets.get('spoolfile', session.experiment.sample.samplename))

    def presetInfo(self):
        return ['exptime', 'number', 'cycletime', 'spoolfile']

    def doStatus(self, maxage=0):
        if self._started:
            if (self._started + self._counttime) > currenttime():
                return status.BUSY, 'counting'
        return status.OK, 'idle'

    def doStop(self):
        self._started = None
        self.status(0)
        self._attached_fastshutter.maw('closed')

    def doFinish(self):
        self._started = None
        # self._attached_fastshutter.maw('closed')

    def doWriteTriggermode(self, value):
        self._write_command('SetTriggerMode %d' % self.TRIGGER_MODES[value])

    def doWriteShuttermode(self, value):
        self._write_command('SetShutterMode %d' % self.SHUTTER_MODES[value])

    def doWriteSpooldirectory(self, value):
        self._write_command('SetSpoolDirectory %s' % value)

    def doWriteBin(self, value):
        self._write_command('SetHBin %d' % value[0])
        self._write_command('SetVBin %d' % value[1])

    def _write_command(self, command):
        ret = self._dev.communicate(command)
        if not ret.startswith('ACK'):
            if ret.startswith('ERR'):
                raise InvalidValueError(self,
                                        'Command: %s is invalid ' % command)
            raise InvalidValueError(
                self, 'Command: %s has invalid '
                'parameters' % command)

    def _query_value(self, request):
        return self._dev.communicate(request)

    def _write_presets(self, preset, value):
        if preset == 'exptime':
            self._write_command('SetExposureTime %f' % value)
        elif preset == 'number':
            self._write_command('SetKineticNumber %d' % value)
            self._knumber = int(value)
        elif preset == 'cycletime':
            self._write_command('SetKineticCycleTime %f' % value)
        elif preset == 'spoolfile':
            self._write_command('SetSpoolFileName %s' % value)
Example #6
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
Example #7
0
class HasTimeout(DeviceMixinBase):
    """
    Mixin class for devices whose wait() should have a simple timeout.

    Classes using this mixin may provide a `timeoutAction` which gets
    called once as soon as the device times out (and a status is obtained).
    Any Exceptions occurring therein will not stop a script!

    The time at which the device will get a timeout (in case it is still in
    `status.BUSY` or has not reached its target value which corresponds to
    `isAtTarget` being False if implemented) is determined upon calling
    `start` and is saved to the non-userparameter `_timesout`.
    If you need a more dynamic timeout calculation, you may provide a
    `doReadTimeout` method (with volatile=True) to calculate the extra amount
    on the fly.

    The relevant timestamps are internally stored in the (non-userparam)
    `_timesout`, which is either set to None, if there is no nicos-initiated
    movement, or to an iterable of tuples describing what action is supposedly
    performed and at which time it should be finished.
    If there is a doTime method, it is used to calculate the length of an
    intermediary 'ramping' phase and timeout. This may be followed by other
    phases. If `timeout` is not None, a last phase is added which takes
    `timeout` seconds to time out.

    You may set the `timeout` parameter to None in which case the device will
    never time out.
    """
    parameters = {
        'timeout':
        Param('Time limit for the device to reach its target'
              ', or None',
              unit='s',
              fmtstr='%.1f',
              type=none_or(float),
              settable=True,
              mandatory=False,
              chatty=True),
        '_timesout':
        Param('Device movement should finish between these '
              'timestamps',
              type=none_or(nonemptylistof(tupleof(string, float))),
              unit='s',
              userparam=False,
              settable=True),
    }

    # derived classes may redefine this if they need different behaviour
    timeout_status = status.NOTREACHED

    # internal flag to determine if the timeout action has been executed
    _timeoutActionCalled = False

    @property
    def _startTime(self):
        # only read the parameter once from the cache db to avoid race
        # condition between the checks below
        timesout = self._timesout
        return timesout[0][1] if timesout else None

    @property
    def _timeoutTime(self):
        # see above
        timesout = self._timesout
        return timesout[-1][1] if timesout else None

    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 = [('start', current_time)]
        if hasattr(self, 'doTime'):
            res.append(('', res[-1][1] + self.doTime(current_pos, target_pos)))
        if self.timeout:
            res.append(('', res[-1][1] + self.timeout))
        return res

    def isTimedOut(self):
        """Method to (only) check whether a device's movement timed out or not.

        Returns False unless there was a timeout in which case it returns True.
        """
        if self.timeout is None:
            return False
        timeoutTime = self._timeoutTime
        if timeoutTime is not None:
            remaining = timeoutTime - currenttime()
            if remaining > 0:
                self.log.debug("%.2f s left before timeout", remaining)
            else:
                self.log.debug("timeout since %.2f s", -remaining)
            return remaining < 0
        return False

    def resetTimeout(self, target):
        """Method called to reset the timeout when the device is started to
        a new target.
        """
        self._timeoutActionCalled = False
        timesout = self._getTimeoutTimes(self.read(), target, currenttime())
        self._setROParam('_timesout', timesout)

    def _clearTimeout(self):
        self._setROParam('_timesout', None)

    def _targetReached(self):
        """Clears timeout in order to suppress further timeouts after the
        device has reached its target. This is determined by `_combinedStatus`.
        Thus this will just be checked when the status is polled periodically
        before the device timed out.
        This behaviour may be changed in derived classes,
        e.g. `HasWindowTimeout`.
        """
        self._clearTimeout()

    def _combinedStatus(self, maxage=0):
        """Create a combined status from doStatus, isAtTarget and timedOut

        If a timeout happens, use the status set by self.timeout_status and
        call `timeoutAction` once if defined. Pollers and other `SLAVE`s do
        *not* call `timeoutAction`.
        """
        from nicos.core.device import Readable  # isort:skip
        code, msg = Readable._combinedStatus(self, maxage)

        if code in (status.OK, status.WARN) and self._timeoutTime:
            if not self.isAtTarget(self.read(maxage)):
                code = status.BUSY
                msg = statusString('target not yet reached', msg)
            else:
                self._targetReached()
        if code == status.BUSY:
            if self.isTimedOut():
                code = self.timeout_status
                msg = statusString('movement timed out', msg)
                # only call once per timeout, flag is reset in Device.start()
                if not self._timeoutActionCalled and \
                        session.mode in (MASTER, MAINTENANCE):
                    try:
                        if hasattr(self, 'timeoutAction'):
                            self.timeoutAction()
                            self._timeoutActionCalled = True
                            return self._combinedStatus(maxage)
                    except Exception:
                        self.log.exception('error calling timeout action',
                                           exc=True)
                    finally:
                        self._timeoutActionCalled = True
            elif self._timesout:
                # give indication about the phase of the movement
                for m, t in self._timesout or []:
                    if t > currenttime():
                        msg = statusString(m, msg)
                        break
        elif code == status.ERROR:
            self._timeoutActionCalled = True

        return (code, msg)
Example #8
0
class MirrorSample(Sample):

    parameters = {
        'length':
        Param('Length of the mirror',
              type=floatrange(0),
              settable=True,
              userparam=True,
              unit='mm',
              default=200),
        'thickness':
        Param('Thickness of the mirror',
              type=floatrange(0),
              settable=True,
              userparam=True,
              unit='mm',
              default=8),
        'height':
        Param('Height of the mirror',
              type=floatrange(0),
              settable=True,
              userparam=True,
              unit='mm',
              default=32),
        'm':
        Param('Reflection index (m)',
              type=floatrange(1),
              settable=True,
              userparam=True,
              unit='',
              default=2),
        'alfa':
        Param('Linear decrease of reflectivity m > 1',
              type=floatrange(0),
              settable=True,
              userparam=True,
              unit='',
              default=4.25),
        'waviness':
        Param('Waviness of the mirror surface',
              type=floatrange(0),
              settable=True,
              userparam=True,
              unit='deg',
              default=0.01),
        'rflfile':
        Param('Reflectivity file',
              type=none_or(str),
              settable=True,
              userparam=True,
              default=''),
    }

    def _applyParams(self, number, parameters):
        """Apply sample parameters.
        """
        Sample._applyParams(self, number, parameters)
        if 'length' in parameters:
            self.length = parameters['length']
        if 'thickness' in parameters:
            self.thickness = parameters['thickness']
        if 'height' in parameters:
            self.height = parameters['height']
        if 'm' in parameters:
            self.m = parameters['m']
        if 'alfa' in parameters:
            self.alfa = parameters['alfa']
        if 'waviness' in parameters:
            self.waviness = parameters['waviness']
        if 'rflfile' in parameters:
            self.rflfile = parameters['rflfile']