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')
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)
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)
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),)), }
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)
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
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)
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']