class AsciiScanfileSink(FileSink): """A data sink that writes to a plain ASCII data file.""" parameters = { 'commentchar': Param('Comment character', type=str, default='#', settable=True), 'semicolon': Param('Whether to add a semicolon between X and Y ' 'values', type=bool, default=True), } handlerclass = AsciiScanfileSinkHandler parameter_overrides = { 'settypes': Override(default=[SCAN, SUBSCAN]), 'filenametemplate': Override(default=['%(proposal)s_%(scancounter)08d.dat']), } def doUpdateCommentchar(self, value): if len(value) > 1: raise ConfigurationError('comment character should only be ' 'one character')
class SafetyInputs(Readable): """The input devices for the safety system. The safety system has a lot of status lines which are connected to a separate input which is integrated into the NICOS system. """ attached_devices = { 'i7053': Attach('three 7053 modules', DigitalInput, multiple=3), } parameter_overrides = { 'unit': Override(mandatory=False), 'fmtstr': Override(default='%d'), 'maxage': Override(default=0), } def _readHWState(self, maxage=0): state = 0 for i, sdev in enumerate(self._attached_i7053): state |= (sdev.read(maxage) << (16 * i)) return state def doRead(self, maxage=0): state = self._readHWState(maxage) self.log.info('val description') for i, bit in enumerate(bin(state)[2:][::-1]): self.log.info('%s %s', bit, bit_description[i]) return state def doStatus(self, maxage=0): # XXX define which bits may be active for normal state state = self._readHWState(maxage) return status.OK, str(state)
class TimerChannelMixin(DeviceMixinBase): """Mixin for channels that return measured time.""" is_timer = True parameter_overrides = { 'unit': Override(default='s'), 'fmtstr': Override(default='%.2f'), } def valueInfo(self): return Value(self.name, unit=self.unit, type='time', fmtstr=self.fmtstr), def doTime(self, preset): if self.ismaster: return self.preselection return 0 def doSimulate(self, preset): if self.ismaster: return [self.preselection] return [0.0]
class BeamElement(HasTimeout, Moveable): """ Class for readout of the MIRA shutter via digital input card, and closing the shutter via digital output (tied into Pilz security system). """ valuetype = oneof('in', 'out') attached_devices = { 'valve': Attach('in/out pressure valve', Moveable), 'switch_in': Attach('limit switch for "in" position', Readable), 'switch_out': Attach('limit switch for "out" position', Readable), } parameter_overrides = { 'timeout': Override(default=10), 'unit': Override(mandatory=False, default=''), } def doStatus(self, maxage=0): is_in = self._attached_switch_in.read(maxage) is_out = self._attached_switch_out.read(maxage) valvepos = self._attached_valve.read(maxage) if (is_in and valvepos == 'in') or (is_out and valvepos == 'out'): return status.OK, 'idle' return status.BUSY, 'moving' def doRead(self, maxage=0): return self._attached_valve.read(maxage) def doStart(self, target): self._attached_valve.start(target) def doReset(self): multiReset(self._adevs)
class CCRSwitch(Moveable): """Class for FRM II sample environment CCR box switches (gas/vacuum). """ attached_devices = { 'write': Attach('TACO digital output device', DigitalOutput), 'feedback': Attach('TACO digital input device (feedback)', DigitalInput), } parameter_overrides = { 'fmtstr': Override(default='%d'), 'unit': Override(default='', mandatory=False), } valuetype = oneofdict({'on': 1, 'off': 0}) def doStart(self, target): if self.read(0) != target: self._attached_write.start(1) def doStatus(self, maxage=0): return status.OK, '' def doRead(self, maxage=0): return self._attached_feedback.read(maxage)
class SdsRatemeter(JsonBase): """Read the count rates for the different input channels of the SDS.""" parameters = { 'channel': Param('Channel to be rated', type=oneof('a', 'x', 'y'), default='a', settable=False) } parameter_overrides = { 'fmtstr': Override(default='%d'), 'unit': Override(default='cps'), } def doStatus(self, maxage=0): try: res = self._read_controller([self.valuekey]) if int(res[self.valuekey]) == 0: return status.OK, '' else: return status.ERROR, 'System tripped! please clear' except CommunicationError: return status.WARN, 'Timeout during talk to the hardware.' except NicosError: return status.ERROR, 'Could not talk to hardware.' def doRead(self, maxage=0): res = self._read_controller(['mon_counts_cps_%s' % self.channel]) res = int(res.values()[0]) ret = int(res / 2.46) self.log.info('system %dfoo countrate %dcps', res, ret) return ret
class AsymmetricMagnet(HasTimeout, CurrentSupply): """Class for the asymmetric ccmsans. Provides the ability to set the current field, and the asymmetry ratio. """ parameters = { 'asymmetry': Param('Asymmetry ratio', type=oneof(0.0, 0.11, 0.25, 0.39, 0.53, 0.70), settable=True, volatile=True), } parameter_overrides = { # default timeout: doTime() + 5 mins 'timeout': Override(mandatory=False, default=300), } parameter_overrides = { # max range * max ramp + 5' 'timeout': Override(mandatory=False, default=5400 + 300) } busystates = (status.BUSY, status.ERROR) valuetype = float def doReadAsymmetry(self): return float( self._taco_guard(self._dev.deviceQueryResource, 'asymmetry')) def doWriteAsymmetry(self, value): self._taco_update_resource('asymmetry', str(value))
class VirtualTemperature(VirtualMotor): """A virtual temperature regulation device.""" parameters = { 'setpoint': Param('Last setpoint', settable=True, unit='main', category='general'), } parameter_overrides = { 'unit': Override(mandatory=False, default='K'), 'jitter': Override(default=0.1), 'curvalue': Override(default=10), 'precision': Override(default=1), } def doStart(self, pos): self.setpoint = pos VirtualMotor.doStart(self, pos) def _step(self, start, end, elapsed, speed): # calculate an exponential approximation to the setpoint with a time # constant given by self.speed gamma = speed / 10. cval = end + (start - end) * exp(-gamma*elapsed) if abs(cval - end) < self.jitter: return end return cval
class BeamStopAxis(Axis): """special Axis, which always has an offset of 0 """ parameter_overrides = { 'fixed': Override(type=oneof(''), default=''), 'offset': Override(type=oneof(0.0), default=0.0), 'lowlevel': Override(default=True), 'maxtries': Override(default=1000, settable=False), } def doInit(self, mode): Axis.doInit(self, mode) if self.maxtries < 1000: self._setROParam('maxtries', 1000) self._setROParam('userlimits', self.abslimits) if mode not in (SIMULATION, SLAVE ) and self._attached_motor.status()[0] != status.BUSY: self._attached_motor.doSetPosition(self._attached_coder.read()) self._attached_motor.userlimits = self._attached_motor.abslimits def doReadOffset(self): return 0. def doWriteOffset(self, value): return 0. def doReadUserlimits(self): return Axis.doReadAbslimits(self)
class YamlDatafileSink(AsciiScanfileSink): """A data sink that writes to a YAML compatible data file. The `lastpoint` parameter is managed automatically. The current file counter as well as the name of the most recently written scanfile is managed by the experiment device. """ handlerclass = YamlDatafileSinkHandler parameters = { 'filenametemplate': Param( 'Name template for the files written', type=listof(str), userparam=False, settable=False, default=['%(proposal)s_' '%(scancounter)08d.yaml'], ), } parameter_overrides = { 'settypes': Override(default=[SCAN, SUBSCAN]), 'semicolon': Override(default=''), 'commentchar': Override(default='', ) }
class Detector(Moveable): """Combination device for the detector axes.""" valuetype = tupleof(float, float, float) attached_devices = { 'x': Attach('X motor', Moveable), 'y': Attach('Y motor', Moveable), 'z': Attach('Z motor', Moveable), } parameter_overrides = { 'unit': Override(mandatory=False, default='mm'), 'fmtstr': Override(default='%.1f, %.2f, %.0f'), } def doRead(self, maxage=0): return (self._attached_x.read(maxage), self._attached_y.read(maxage), self._attached_z.read(maxage)) def doIsAllowed(self, pos): for (i, name, dev) in [(0, 'x', self._attached_x), (1, 'y', self._attached_y), (2, 'z', self._attached_z)]: ok, why = dev.isAllowed(pos[i]) if not ok: return False, name + ': ' + why return True, '' def doStart(self, pos): self._attached_x.start(pos[0]) self._attached_y.start(pos[1]) self._attached_z.start(pos[2])
class Sans1ColliSwitcher(Switcher): """Switcher, specially adapted to Sans1 needs""" parameter_overrides = { 'precision': Override(default=0.1, mandatory=False), 'fallback': Override(default='Unknown', mandatory=False), 'blockingmove': Override(default='False', mandatory=False), } def _mapReadValue(self, pos): """Override default inverse mapping to allow a deviation <= precision""" prec = self.precision def myiter(mapping): # use position names beginning with P as last option for name, value in mapping.items(): if name[0] != 'P': yield name, value for name, value in mapping.items(): if name[0] == 'P': yield name, value for name, value in myiter(self.mapping): if prec: if abs(pos - value) <= prec: return name elif pos == value: return name if self.fallback is not None: return self.fallback if self.relax_mapping: return self._attached_moveable.format(pos, True) raise PositionError(self, 'unknown position of %s' % self._attached_moveable)
class ComtecHeaderSink(ImageSink): """Base class for sinks that save arrays to "image" files.""" attached_devices = { 'detector': Attach('Fast Detector', ComtecTimer), } parameters = { 'fast_basepaths': Param('Mount point(s) of the fast data storage', type=listof(str), default=['/'], settable=False), } parameter_overrides = { 'settypes': Override(default=[POINT, SCAN]), 'filenametemplate': Override(mandatory=False, settable=False, userparam=False, default=COMTEC_TEMPLATES), 'subdir': Override(default='comtec'), } handlerclass = ComtecHeaderSinkHandler def doInit(self, mode): if mode != SIMULATION: # XXX: check existence of 'fast_basepath' pass
class ComtecCounter(CounterChannelMixin, TacoBaseChannel, PassiveChannel): taco_class = Counter parameter_overrides = { 'type': Override(type=oneof('counter'), mandatory=False, default='counter'), 'mode': Override(type=oneof('normal'), mandatory=False, default='normal'), 'fmtstr': Override(default='%d'), } def doReadMode(self): return 'normal' def doWriteMode(self, value): return 'normal' def doReadIsmaster(self): return False def doWriteIsmaster(self, value): return False def valueInfo(self): return Value(self.name, unit='cts', errors='sqrt', type='counter', fmtstr='%d'),
class McStasImage(BaseImage): parameter_overrides = { 'size': Override(default=(625, 450)), 'mcstasprog': Override(default='biodiff_fast'), 'mcstasfile': Override(default='PSD_BIODIFF_total.psd'), 'writedelay': Override(default=0.3), } attached_devices = { 'sample': Attach('Mirror sample', SXTalSample), 's1': Attach('Slit 1', Readable), 's2': Attach('Slit 2', Readable), 'omega': Attach('Sample omega rotation', Readable), 'wavelength': Attach('Incoming wavelength', Readable), # 'sample_x': Attach('Sample position x', Readable), # 'sample_y': Attach('Sample position y', Readable), # 'sample_z': Attach('Sample position z', Readable), # 'beamstop': Attach('Beam stop positon', Readable), } def _prepare_params(self): params = [] params.append('SLIT_S1=%s' % self._attached_s1.read(0)) params.append('SLIT_S2=%s' % self._attached_s2.read(0)) params.append('omega=%s' % self._attached_omega.read(0)) sample = self._attached_sample params.append('cell_a=%s' % sample.a) params.append('cell_b=%s' % sample.b) params.append('cell_c=%s' % sample.c) params.append('Lam=%s' % self._attached_wavelength.read(0)) params.append('dLam=%s' % 0.05) params.append('REP=%s' % 1000) return params
class Switcher(GenericSwitcher): """ Switcher, specially adapted to TOFTOF needs The neutron guide switcher has two different guides and the job is to change between them. Since there is no encoder mounted to check the position each change has to start with a reference move, followed by the move to the target position. """ parameter_overrides = { 'precision': Override(default=0.1, mandatory=False), 'fallback': Override(default='Unknown', mandatory=False), 'blockingmove': Override(default='False', mandatory=False), } def _startRaw(self, target): """Initiate movement of the moveable to the translated raw value.""" if isinstance(self._attached_moveable, CanReference): self.log.info('referencing %s...', self._attached_moveable) self._attached_moveable.reference() else: self.log.warning('%s cannot be referenced!', self._attached_moveable) self._attached_moveable.start(target) if self.blockingmove: self._attached_moveable.wait()
class CaressScanfileSink(FileSink): """A data sink that writes the CARESS file format.""" handlerclass = CaressScanfileSinkHandler parameters = { # Since some devices gives strings back as read values, but the CARESS # format does not accept strings they must be mapped to numbers 'mapping': Param('Mapping of (string) device values to float values', unit='', settable=False, type=dictof(str, float), default={ 'on': 1, 'off': 0, 'open': 1, 'closed': 0 }), } parameter_overrides = { 'settypes': Override(default=[SCAN, SUBSCAN]), # 'settypes': Override(default=[SCAN, SUBSCAN, POINT]), 'filenametemplate': Override( default=['m2%(pointcounter)08d.dat', 'm2%(scancounter)08d.dat']), }
class IPCSwitches(Input): """ IPC motor card read out for the limit switches and reference switch """ parameter_overrides = { 'first': Override(mandatory=False, default=5, settable=False), 'last': Override(mandatory=False, default=7, settable=False), } def doInit(self, mode): Input.doInit(self, mode) # init _mask if mode != SIMULATION: self._attached_bus.ping(self.addr) def doStatus(self, maxage=0): try: self._attached_bus.get(self.addr, 134) return status.OK, '' except NicosError: return status.ERROR, 'Hardware not found' def doRead(self, maxage=0): """ returns 0 if no switch is set 1 if the lower limit switch is set 2 if the upper limit switch is set 4 if the reference switch is set """ try: # temp & 32 'low limit switch' # temp & 64 'high limit switch' # temp & 128 'ref switch' temp = self._attached_bus.get(self.addr, 134) return (temp & self._mask) >> self.first except Exception as err: raise NicosError(self, 'cannot evaluate status byte of stepper') from err
class Lenses(Moveable): """High-level lens control.""" valuetype = oneof(*LENS_CONFIGS) hardware_access = False attached_devices = { 'io': Attach('Lens I/O device', Moveable), } parameters = { 'values': Param('Possible values (for GUI)', internal=True, type=listof(str), default=LENS_CONFIGS), } parameter_overrides = { 'fmtstr': Override(default='%s'), 'unit': Override(mandatory=False, default=''), } def doRead(self, maxage=0): lens_read = int(self._attached_io.read(maxage)) configs = [('in' if lens_read & (1 << i) else 'out') for i in range(3)] return '-'.join(configs) def doStart(self, target): configs = [(v == 'in') for v in target.split('-')] bits = configs[0] + 2 * configs[1] + 4 * configs[2] self._attached_io.start(bits)
class CounterChannelMixin(DeviceMixinBase): """Mixin for channels that return a single counts value.""" is_timer = False parameters = { 'type': Param('Type of channel: monitor or counter', type=oneof('monitor', 'counter', 'other'), mandatory=True), } parameter_overrides = { 'unit': Override(default='cts'), 'fmtstr': Override(default='%d'), 'preselection': Override(type=int), } def valueInfo(self): return Value(self.name, unit=self.unit, errors='sqrt', type=self.type, fmtstr=self.fmtstr), def doSimulate(self, preset): if self.ismaster: return [int(self.preselection)] return [0]
class Spectrum(VirtualImage): parameters = { 'preselection': Param('Preset value for this channel', type=float, settable=True), } parameter_overrides = { 'sizes': Override(type=tupleof(intrange(1, 1), intrange(1, 16384)), default=(1, 16384)), 'ismaster': Override(settable=True), } # set to True to get a simplified doEstimateTime is_timer = False def doEstimateTime(self, elapsed): if not self.ismaster or self.doStatus()[0] != status.BUSY: return None if self.is_timer: return self.preselection - elapsed else: counted = float(self.doRead()[0]) # only estimated if we have more than 3% or at least 100 counts if counted > 100 or counted > 0.03 * self.preselection: if 0 <= counted <= self.preselection: return (self.preselection - counted) * elapsed / counted def doReadArray(self, _quality): return self._buf[0]
class UBahn(Readable): parameters = { 'station': Param('Name of the U-Bahn station', type=str, settable=True, default='Garching-Forschungszentrum'), } parameter_overrides = { 'unit': Override(mandatory=False, default='min'), 'fmtstr': Override(default='%s'), 'pollinterval': Override(default=60), 'maxage': Override(default=70), } def doRead(self, maxage=0): if parse is None: raise NicosError(self, 'cannot parse web page, lxml is not ' 'installed on this system') try: tree = parse(URL % self.station) return ', '.join(n.text for n in tree.findall('//td[@class="inMinColumn"]')) except Exception as err: raise NicosError(self, 'MVG site not responding or changed format:' ' %s' % err) def doStatus(self, maxage=0): return status.OK, ''
class RectROIChannel(PostprocessPassiveChannel): """Calculates counts for a rectangular region of interest.""" parameters = { 'roi': Param('Rectangular region of interest (x, y, width, height)', tupleof(int, int, int, int), settable=True, category='general'), } parameter_overrides = { 'unit': Override(default='cts'), 'fmtstr': Override(default='%d'), } def getReadResult(self, arrays, _results, _quality): if any(self.roi): x, y, w, h = self.roi return [arr[y:y + h, x:x + w].sum() for arr in arrays] return [arr.sum() for arr in arrays] def valueInfo(self): if len(self.readresult) > 1: return tuple( Value(name=self.name + '[%d]' % i, type='counter', fmtstr='%d') for i in range(1, len(self.readresult) + 1)) return Value(name=self.name, type='counter', fmtstr='%d'),
class LinearROIChannel(PostprocessPassiveChannel): """Calculates counts for a region of interest in a 1D spectrum.""" parameters = { 'roi': Param('Region of interest (start, end) including end', tupleof(int, int), settable=True, category='general'), } parameter_overrides = { 'unit': Override(default='cts'), 'fmtstr': Override(default='%d'), } def getReadResult(self, arrays, _results, _quality): if any(self.roi): return [arr[self.roi[0]:self.roi[1] + 1].sum() for arr in arrays] return [arr.sum() for arr in arrays] def valueInfo(self): if len(self.readresult) > 1: return tuple( Value(name=self.name + '[%d]' % i, type='counter', fmtstr='%d') for i in range(1, len(self.readresult) + 1)) return Value(name=self.name, type='counter', fmtstr='%d'),
class ResedaHDF5Sink(FileSink): parameters = { 'subscangrouptmpl': Param('Name of the sub scan specific group', type=str, default='echo_%(subscancounter)d'), 'scalarstmpl': Param('Name of the scalars dataset', type=str, default='scalars_%(pointcounter)d'), 'imagetmpl': Param('Name of the image dataset', type=str, default='image_%(pointcounter)d'), } parameter_overrides = { 'filenametemplate': Override(mandatory=False, settable=False, userparam=False, default=['%(scancounter)08d.hdf5']), 'settypes': Override(default=[SCAN, SUBSCAN]), } handlerclass = ResedaHDF5SinkHandler def doInit(self, mode): self._current_scan_file = None def isActive(self, dataset): if dataset.settype != SCAN and self._current_scan_file is None: return False return FileSink.isActive(self, dataset)
class Input(Readable): """IPC I/O card digital input class.""" parameters = { 'addr': Param('Bus address of the card', type=intrange(32, 255), mandatory=True), 'first': Param('First bit to read', type=intrange(0, 15), mandatory=True), 'last': Param('Last bit to read', type=intrange(0, 15), mandatory=True), } parameter_overrides = { 'unit': Override(mandatory=False, default=''), 'fmtstr': Override(default='%d'), } attached_devices = { 'bus': Attach('The communication bus', IPCModBus), } def doInit(self, mode): self._mask = ((1 << (self.last - self.first + 1)) - 1) << self.first def doRead(self, maxage=0): high = self._attached_bus.get(self.addr, 181) << 8 low = self._attached_bus.get(self.addr, 180) return ((high + low) & self._mask) >> self.first def doStatus(self, maxage=0): return status.OK, ''
class FPLCTrigger(HasTimeout, Moveable): """Trigger the FPLC flow and then wait for the sample to be ready inside the cell. Used as a sample environment device in kwscount(). """ valuetype = oneof('triggered', 'waiting') hardware_access = True attached_devices = { 'output': Attach('start output to FPLC', Moveable), 'input': Attach('trigger input from FPLC', Readable), } parameters = { 'started': Param('Time when device was started', internal=True, settable=True), 'triggered': Param('Time when input was triggered after a start', internal=True, settable=True), } parameter_overrides = { 'fmtstr': Override(default='%s'), 'timeout': Override(default=120), 'unit': Override(mandatory=False, default=''), } def doInit(self, mode): if mode == MASTER: self.triggered = self.started = 0 def doStart(self, target): if target == 'triggered': self._attached_output.start(1) sleep(0.1) self._attached_output.start(0) self.started = currenttime() def doStatus(self, maxage=0): if self.started: if self.mode == MASTER and self._attached_input.read(maxage): self.started = 0 self.triggered = currenttime() return status.OK, 'triggered' else: return status.BUSY, 'waiting' elif self.triggered: if self.mode == MASTER and currenttime() > self.triggered + 5: self.triggered = 0 return status.OK, 'triggered' return status.OK, '' def doRead(self, maxage=0): if self.started: return 'waiting' return 'triggered'
class PolSwitcher(SequencerMixin, MultiSwitcher): """The turntable that contains the polarizer or neutron guide. Changing the table positions has to be done in a certain order, so that the final positions of the polarizer or guide are always reproducible. """ parameters = { 'movepos': Param('Position (xv, yv, xh, yh) while rotating', type=tupleof(float, float, float, float), default=(5., 5., 5., 5.)), } parameter_overrides = { 'fmtstr': Override(default='%s'), 'unit': Override(mandatory=False, default=''), } def doInit(self, mode): MultiSwitcher.doInit(self, mode) if len(self._attached_moveables) != 5: raise ConfigurationError(self, 'must have exactly 5 moveables') self._mot_rot, self._mot_xv, self._mot_yv, self._mot_xh, \ self._mot_yh = self._attached_moveables self.valuetype = oneof(*sorted(self.mapping, key=num_sort)) def _generateSequence(self, target): seq = [] targets = self.mapping[target] rot_target, xv_target, xh_target, yv_target, yh_target = targets # move translation units to move pos (in parallel) seq.append(tuple(SeqDev(m, p) for (m, p) in zip(self._attached_moveables[1:], self.movepos))) # move rotation stage seq.append(SeqDev(self._mot_rot, rot_target)) # move Y axes to final position with backlash seq.append((SeqDev(self._mot_yv, yv_target + 0.1), SeqDev(self._mot_yh, yh_target + 0.1))) seq.append(SeqDev(self._mot_yv, yv_target)) seq.append(SeqDev(self._mot_yh, yh_target)) # move X axes to 0.1 and then to final position seq.append((SeqDev(self._mot_xv, 0.1), SeqDev(self._mot_xh, 0.1))) seq.append(SeqDev(self._mot_xv, xv_target)) seq.append(SeqDev(self._mot_xh, xh_target)) # move rotation stage again, if it changed seq.append(SeqDev(self._mot_rot, rot_target)) return seq def doStart(self, target): if self._seq_is_running(): if self._mode == SIMULATION: self._seq_thread.join() self._seq_thread = None else: raise MoveError(self, 'Cannot start device, sequence is still ' 'running (at %s)!' % self._seq_status[1]) self._startSequence(self._generateSequence(target))
class VoltageSupply(PowerSupply): """work around a bug either in the taco server or in the hv supply itself basically the idle status is returned at the end of the ramp, even if the output voltage is nowhere near the target value """ parameters = { '_stopflag': Param('Supply was stopped', type=bool, settable=True, mandatory=False, internal=True, default=False), } parameter_overrides = { 'timeout': Override(default=90), 'precision': Override(volatile=True), } _last_st = status.OK, '' def timeoutAction(self): if self.target is not None: self.log.warning('Timeout! retrying once to reach %s', self.format(self.target, unit=True)) # start() would clear timeoutActionCalled Flag self.start(self.target) def doStart(self, target): self._stopflag = False PowerSupply.doStart(self, target) def doStatus(self, maxage=0): # suppress intermittent tripped messages st = PowerSupply.doStatus(self, maxage) if st[0] == status.ERROR and 'trip' in st[1]: if 'trip' not in self._last_st[1]: st = (status.WARN, st[1]) self._last_st = st return st def doStop(self): self._stopflag = True PowerSupply.doStop(self) self.wait() PowerSupply.doStart(self, self.read(0)) self.wait() def doReset(self): self._stopflag = False self._dev.Reset() def doReadPrecision(self): if self.target == 1: return 69 return 3
class Polarizer(HasTimeout, Moveable): """Controls both the position of the polarizer and the spin flipper. """ valuetype = oneof(*POL_SETTINGS) hardware_access = True attached_devices = { 'output': Attach('output setter', Moveable), 'input_in': Attach('input for limit switch "in" position', Readable), 'input_out': Attach('input for limit switch "out" position', Readable), 'flipper': Attach('3He flipper', Moveable), } parameter_overrides = { 'fmtstr': Override(default='%s'), 'timeout': Override(default=10), 'unit': Override(mandatory=False, default=''), } parameters = { 'values': Param('Possible values (for GUI)', internal=True, type=listof(str), default=POL_SETTINGS), } def doStatus(self, maxage=0): is_in = self._attached_input_in.read(maxage) is_out = self._attached_input_out.read(maxage) # check individual bits if is_in ^ is_out != 3: # inconsistent state, check switches if ((is_in & 2) and (is_out & 2)) or \ ((is_in & 1) and (is_out & 1)): # both switches on? return status.ERROR, 'both switches on for element(s)' return status.BUSY, 'elements moving' # HasTimeout will check for target reached return self._attached_flipper.status(maxage) def doRead(self, maxage=0): is_in = self._attached_input_in.read(maxage) if is_in == 3: return self._attached_flipper.read() elif is_in > 0: return 'inconsistent' return 'out' def doStart(self, target): if target == 'out': self._attached_output.start(0) else: self._attached_output.start(3) self._attached_flipper.start(target)