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 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 DimetixLaser(CanDisable, HasOffset, Readable): attached_devices = { 'signal': Attach('signal strength device', Readable), 'value': Attach('value device', Readable), } parameter_overrides = { 'unit': Override(volatile=True, mandatory=False), } parameters = { 'signallimit': Param('minimal signal strength for valid reading', type=floatrange(0.), default=8000), 'invalidvalue': Param('value to indicate invalid value', type=intrange(-2000, -2000), default=-2000), } def doRead(self, maxage=0): if self._attached_signal.read(maxage) < self.signallimit: return self.invalidvalue return self._attached_value.read(maxage) def doReadUnit(self): return self._attached_value.unit def doPoll(self, n, maxage): self._attached_signal.poll(n, maxage) self._attached_signal.poll(n, maxage)
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 SampleIllumination(Readable): parameters = { 's1pos': Param('Slit 1 position relative to sample', mandatory=True, type=float), 's2pos': Param('Slit 2 position relative to sample', mandatory=True, type=float), 'f0': Param('Footprint for n * pi, 0 <= n <= N', type=float, default=nan) } attached_devices = { 's1': Attach('First slit', Slit), 's2': Attach('Second slit', Slit), 'theta': Attach('Incidence angle', Readable) } def doRead(self, maxage=0): """Calculate the beam footprint (illumination on the sample).""" # l1= distance between slits l1 = self.s1pos - self.s2pos # l2 distance from last slit to sample l2 = self.s2pos # get slit width s1w = self._attached_s1.width.read(maxage) s2w = self._attached_s2.width.read(maxage) theta = self._attached_theta.read(maxage) denominator = abs(sin(radians(theta))) if denominator < 1e-4: return self.f0 return (min(s1w, s2w) + l2 / l1 * (s1w + s2w)) / denominator
class PropertyChanger(Moveable): """This is essentially a ParamDevice and can be replace once Controller uses single setters (NICOS-style interface). """ attached_devices = { 'chopper': Attach('Chopper controller', BaseChopperController), 'chdelay': Attach('Setting chopper delay', Moveable), } def doStatus(self, maxage=0): stat = self._attached_chopper.status(maxage) if stat[0] != status.OK: return stat[0], 'changing' return status.OK, 'idle' def doRead(self, maxage=0): return getattr(self._attached_chopper, self._prop) def doStart(self, target): ch5_90deg_offset = self._attached_chopper.ch5_90deg_offset chwl, chspeed, chratio, chst = self._chopper_params(target) _chdelay = calc.calculateChopperDelay(chwl, chspeed, chratio, chst, ch5_90deg_offset) self.log.debug('setting chopper delay to: %d', _chdelay) self._attached_chdelay.move(_chdelay) self._attached_chopper._change(self._prop, target) def doReadTarget(self): return getattr(self._attached_chopper, self._prop)
class TestController(IsController, Moveable): attached_devices = { 'dev1': Attach('First device', Moveable), 'dev2': Attach('Second device', Moveable), } def isAdevTargetAllowed(self, adev, adevtarget): if adev == self._attached_dev1: other = self._attached_dev2.read() if other < adevtarget: return (False, 'dev1 can only move to values smaller' ' than %r' % other) if adev == self._attached_dev2: other = self._attached_dev1.read() if other > adevtarget: return (False, 'dev2 can only move to values greater' ' than %r' % other) return (True, 'Allowed') def doRead(self, maxage=0): return self._value def doIsAllowed(self, target): if target[0] > target[1]: return (False, 'dev1 can only move to values greater' ' than dev2') return (True, 'Allowed') def doStart(self, target): self._value = target self._attached_dev1.start(target[0]) self._attached_dev2.start(target[1])
class LiftingSXTal(SXTalBase): attached_devices = { 'gamma': Attach('gamma device', Moveable), 'omega': Attach('omega device', Moveable), 'nu': Attach('nu device (counter lifting axis)', Moveable), } def _extractPos(self, pos): return [ ('gamma', np.rad2deg(pos.gamma)), ('omega', np.rad2deg(pos.omega)), ('nu', np.rad2deg(pos.nu)), ] def _convertPos(self, pos, wavelength=None): return pos.asL(wavelength) def _readPos(self, maxage=0): return PositionFactory('l', gamma=self._attached_gamma.read(maxage), omega=self._attached_omega.read(maxage), nu=self._attached_nu.read(maxage)) def _createPos(self, gamma, omega, nu): return PositionFactory('l', gamma=gamma, omega=omega, nu=nu) def getScanWidthFor(self, hkl): """Get scanwidth. TODO: make this dependent on angles. """ return 5.0
class NiagExpShutter(NiagShutter): """ the "NiagExpShutter add the shutter speed control functionality to the NiagShutter base class, implemented as an additional parameter called 'fast' It uses 3 additional digital IOs: - 2 outputs to switch to the slow and fast modes, respectively - 1 input to check which mode is active (slow = 0, fast = 1) """ attached_devices = { 'do_fast': Attach('Output to set the shutter speed to fast', Moveable), 'do_slow': Attach('Output to set the shutter speed to slow', Moveable), 'is_fast': Attach('Input to check if the shutter speed is fast', Readable), } parameters = { 'fast': Param('Fast shutter opening/closing', mandatory=True, default=False, settable=True, type=bool, volatile=True), } def doReadFast(self): return self._attached_is_fast.read(0) == 0 # depending on the value, send the pulse to the corresponding output def doWriteFast(self, value): if value: self._attached_do_fast.move(1) else: self._attached_do_slow.move(1)
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 ListmodeSink(QMesyDAQSink): handlerclass = ListmodeSinkHandler attached_devices = { 'liveimage': Attach('device to set filename', Device, optional=True), 'tofchannel': Attach('device to get TOF settings', Device), }
class BeamstopSequencer(BaseSequencer): """ Base class for Beamstop handling classes at SANS """ attached_devices = { 'x': Attach('Motor for X movement', Motor), 'y': Attach('Motor for Y movement', Motor), } parameters = { '_in_x': Param('IN x position', type=float, default=0.0, settable=True, internal=True), '_in_y': Param('IN y position', type=float, default=0.0, settable=True, internal=True), } def _fix(self): self._attached_x.fix() self._attached_y.fix() def _release(self): self._attached_x.release() self._attached_y.release() def _save_pos(self): self._in_x = self._attached_x.read(0) self._in_y = self._attached_y.read(0)
class VSSpeed(BaseSequencer): """ This class controls the speed of the velocity selector. While the readback is a plain analog readable, setting the set point is special: The 12 bit set point is split across two individual bytes of a digital output port. """ attached_devices = { 'state': Attach('Device for controlling the state of the VS', Moveable), 'setp': Attach('Device for setting the velocity setpoint', Moveable), 'rbv': Attach('Readback for the speed', Readable) } _target = None def isAllowed(self, pos): test, reason = self._attached_state.isAllowed('on') if not test: return test, reason if pos < 0 or pos > 124.35: return False, 'Pos outside range 0 - 124.35 Hz' return True, '' def _generateSequence(self, target): seq = [] if target < 5: seq.append(SeqDev(self._attached_setp, 0)) seq.append(SeqWaitValue(self._attached_rbv, 0, 5)) seq.append(SeqDev(self._attached_state, 'off')) elif abs(self._attached_rbv.read(0) - target) >= 1.0: seq.append(SeqDev(self._attached_state, 'on')) seq.append(SeqDev(self._attached_setp, target)) seq.append(SeqWaitValue(self._attached_rbv, target, 1.0)) # Repeat enough of this pair until sufficient stabilisation # has been reached seq.append(SeqSleep(5)) seq.append(SeqWaitValue(self._attached_rbv, target, 1.0)) seq.append(SeqSleep(5)) seq.append(SeqWaitValue(self._attached_rbv, target, 1.0)) seq.append(SeqSleep(5)) seq.append(SeqWaitValue(self._attached_rbv, target, 1.0)) return seq def doRead(self, maxage=0): readval = self._attached_rbv.read(maxage) if readval > 5: return readval return 0 def doStatus(self, maxage=0): if self._seq_is_running(): return status.BUSY, 'Ramping up speed' return status.OK, 'Arrived'
class TASConstant(Moveable): """ Common class for TAS k, E and lambda pseudo-devices. """ parameters = { 'scanmode': Param('Scanmode to set', type=oneof(*SCANMODES), mandatory=True), } parameter_overrides = { 'unit': Override(volatile=True), } attached_devices = { 'base': Attach('Device to move (mono or ana)', Moveable), 'tas': Attach('The spectrometer for setting scanmode', TAS), } valuetype = float hardware_access = False def doStatus(self, maxage=0): return self._attached_base.status(maxage) def _getWaiters(self): return [self._attached_base] def _start(self, k): # first drive there, to determine if it is within limits tas = self._attached_tas base = self._attached_base pos = from_k(k, base.unit) base.start(pos) msg = False if tas.scanmode != self.scanmode: tas.scanmode = self.scanmode msg = True if tas.scanconstant != pos: tas.scanconstant = pos msg = True return msg def doReadUnit(self): # needed for "does volatile param have a doRead" checking raise NotImplementedError def doStop(self): self._attached_base.stop() def fix(self, reason=''): # fix the base as well, avoids surprises Moveable.fix(self, reason) return self._attached_base.fix(reason) def release(self): Moveable.release(self) return self._attached_base.release()
class MariaDetector(Detector): attached_devices = { "shutter": Attach("Shutter to open before exposure", Moveable), "lives": Attach("Live channels", PassiveChannel, multiple=True, optional=True) } parameters = { "ctrl_shutter": Param("Open shutter automatically before " "exposure?", type=bool, settable=True, mandatory=False, default=True), } def doStart(self): # open shutter before exposure if self.ctrl_shutter: self._attached_shutter.maw(OPEN) Detector.doStart(self) def doFinish(self): Detector.doFinish(self) if self.ctrl_shutter and self._attached_shutter.read() == CLOSED: raise InvalidValueError(self, 'shutter not open after exposure, ' 'check safety system') def _getWaiters(self): adevs = dict(self._adevs) if not self.ctrl_shutter: adevs.pop("shutter") return adevs def _presetiter(self): for i, dev in enumerate(self._attached_lives): if i == 0: yield ("live", dev) yield ("live%d" % (i + 1), dev) for itm in Detector._presetiter(self): yield itm def doSetPreset(self, **preset): Detector.doSetPreset(self, **preset) preset = self._getPreset(preset) if not preset: return for dev in self._attached_lives: dev.islive = False for name in preset: if name in self._presetkeys and self._presetkeys[name] and \ name.startswith("live"): dev = self._presetkeys[name] dev.ismaster = True dev.islive = True self.log.debug(" presets: %s", preset) self.log.debug("presetkeys: %s", self._presetkeys) self.log.debug(" masters: %s", self._masters) self.log.debug(" slaves: %s", self._slaves)
class SelectorLambda(Moveable): """ Control selector wavelength directly, converting between speed and wavelength. This uses not the default conversion from the Astrium selector classes, since the selector is tilted against the beam, and it is easier to use the constant determined by wavelength calibration. This class allows two calibration settings, determined by the current value of a (manually moved) "tilt" device. """ parameters = { 'constants': Param( 'Conversion constants: ' 'lam[Ang] = constant/speed[Hz] + offset', type=tupleof(float, float), mandatory=True, unit='Ang Hz'), 'offsets': Param('Conversion offsets: ' 'lam[Ang] = constant/speed[Hz] + offset', type=tupleof(float, float), mandatory=True, unit='Ang'), } attached_devices = { 'seldev': Attach('The selector speed device', Moveable), 'tiltdev': Attach('The tilt device', Readable), } hardware_access = False def doRead(self, maxage=0): tilted = bool(self._attached_tiltdev.read(maxage)) speed = self._attached_seldev.read(maxage) return (60 * self.constants[tilted] / speed) + self.offsets[tilted] \ if speed else -1 def doIsAllowed(self, value): if value == 0: return False, 'zero wavelength not allowed' tilted = bool(self._attached_tiltdev.read(0)) speed = int( round(60 * self.constants[tilted] / (value - self.offsets[tilted]))) return self._attached_seldev.isAllowed(speed) def doStart(self, value): tilted = bool(self._attached_tiltdev.read(0)) speed = int( round(60 * self.constants[tilted] / (value - self.offsets[tilted]))) self.log.debug('moving selector to %f rpm', speed) self._attached_seldev.start(speed)
class MagnetSampleTheta(Moveable): """Class for controlling the sample rotation inside a magnet that is built with significant dark angles that must be avoided for incoming and outgoing beam, by rotating the magnet itself on the sample table. """ attached_devices = { 'sample_theta': Attach('Sample-only theta motor', Moveable), 'magnet_theta': Attach('Magnet-plus-sample motor', Moveable), 'two_theta': Attach('Scattering angle', Moveable), } parameters = { 'blocked': Param('Blocked angle range in the magnet. 0 is the ' 'incoming beam direction', unit='deg', type=listof(tupleof(float, float))), 'windowstep': Param('Steps in which to move the magnet when looking ' 'for free windows', unit='deg', type=int, default=5), } def _find_window(self, gamma, magnet): # find a free window for incoming and outgoing beam, which is closest # to the current position of the magnet result = [] for pos in range(0, 360, self.windowstep): for (a1, a2) in self.blocked: # check for blocked incoming beam if in_range(pos, -a2, -a1): break # check for blocked outgoing beam if in_range(pos, -a2 + 180 + gamma, -a1 + 180 + gamma): break else: # no "break" result.append(pos) self.log.debug('gamma: %.3f, magnet: %.3f', gamma, magnet) self.log.debug('new possible positions: %s', result) if not result: raise ComputationError(self, 'no position found for magnet with ' 'incoming and outgoing beam free') return min(result, key=lambda pos: abs(pos - 0.1)) def doStart(self, pos): # get target for scattering angle gamma = self._attached_two_theta.target magnet = self._attached_magnet_theta.read(0) # determine nearest free window new_magnet = self._find_window(gamma, magnet) self._attached_magnet_theta.start(to_range(new_magnet)) self._attached_sample_theta.start(to_range(pos - new_magnet)) def _getWaiters(self): return [self._attached_sample_theta, self._attached_magnet_theta] def doRead(self, maxage=0): angle = self._attached_magnet_theta.read(maxage) + \ self._attached_sample_theta.read(maxage) return to_range(angle)
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 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)
class SelectorLambda(Moveable): """ Control selector wavelength directly, converting between speed and wavelength. """ parameters = { 'twistangle': Param('Blade twist angle', mandatory=True, unit='deg'), 'length': Param('Selector length', mandatory=True, unit='m'), 'beamcenter': Param('Beam center position', mandatory=True, unit='m'), 'maxspeed': Param('Max selector speed', mandatory=True, unit='rpm'), } attached_devices = { 'seldev': Attach('The selector speed device', Moveable), 'tiltdev': Attach('The tilt angle motor, if present', Moveable, optional=True), } hardware_access = False def _get_tilt(self, maxage): if self._attached_tiltdev: return self._attached_tiltdev.read(maxage) return 0 def _constant(self, tiltang=0): """Calculate the inverse proportional constant between speed (in rpm) and wavelength (in A), depending on the tilt angle (in degrees). Formula adapted from Astrium NVS C++ source code. """ v0 = 3955.98 lambda0 = self.twistangle * 60 * v0 / (360 * self.length * self.maxspeed) A = 2 * self.beamcenter * pi / (60 * v0) return (tan(radians(tiltang)) + (A * self.maxspeed * lambda0)) / \ (-A**2 * self.maxspeed * lambda0 * tan(radians(tiltang)) + A) def doRead(self, maxage=0): spd = self._attached_seldev.read(maxage) return self._constant(self._get_tilt(maxage)) / spd if spd else -1 def doIsAllowed(self, value): if value == 0: return False, 'zero wavelength not allowed' speed = int(self._constant(self._get_tilt(0)) / value) allowed, why = self._attached_seldev.isAllowed(speed) if not allowed: why = 'requested %d rpm, %s' % (speed, why) return allowed, why def doStart(self, value): speed = int(self._constant(self._get_tilt(0)) / value) self.log.debug('moving selector to %d rpm', speed) self._attached_seldev.start(speed)
class KappaGon(IsController, Moveable): ''' Kappa goniometer base class''' attached_devices = { 'ttheta': Attach('Two-theta device', Moveable), 'omega': Attach('omega device', Moveable), 'kappa': Attach('kappa device', Moveable), 'phi': Attach('phi device', Moveable), 'dx': Attach('detector movement device', Moveable), } def doRead(self, maxage=0): return PositionFactory( 'k', ttheta=self._adevs['ttheta'].read(maxage), omega=self._adevs['omega'].read(maxage), kappa=self._adevs['kappa'].read(maxage), phi=self._adevs['phi'].read(maxage), ) def doStart(self, pos): if isinstance(pos, PositionBase): target = pos.asK() self._adevs['ttheta'].start(target.theta * 2.) self._adevs['omega'].start(target.omega) self._adevs['kappa'].start(target.kappa) self._adevs['phi'].start(target.phi) else: raise ValueError( 'incorrect arguments for start, needs to be a PositionBase object' ) def isAdevTargetAllowed(self, adev, adevtarget): if adev == self._adevs['phi']: return True, 'Position allowed' # phi can move freely # for better visual indent # pylint: disable=bad-indentation if adev == self._adevs['kappa']: if (-45 < self._adevs['omega'].target < 135 or 135 < self._adevs['omega'].target < 255): if -10 < adevtarget < 10: return True, 'Position allowed' else: return False, ' -10 < kappa < 10 for this omega position' if adev == self._adevs['omega']: if (self._adevs['ttheta'].target - adevtarget < 45): return False, 'Omega too close to two-theta' else: return True, 'Position OK' if adev == self._adevs['ttheta']: if (adevtarget - self._adevs['omega'].target < 45): return False, 'Omega too close to two-theta' else: return True, 'Position OK' return True, 'Position OK'
class CollimationGuides(HasTimeout, HasLimits, Moveable): """Controlling the collimation guide elements.""" 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), 'sync_bit': Attach('sync bit output', Moveable), } parameters = { 'first': Param('first element controlled', type=int, default=2), } parameter_overrides = { 'fmtstr': Override(default='%d'), 'timeout': Override(default=10), 'unit': Override(mandatory=False, default='m'), 'abslimits': Override(mandatory=False, default=(2, 20)), } def doInit(self, mode): self.valuetype = intrange(self.first, 20) def doStatus(self, maxage=0): is_in = self._attached_input_in.read(maxage) is_out = self._attached_input_out.read(maxage) # check individual bits for i in range(20 - self.first): mask = 1 << i if is_in & mask == is_out & mask: # inconsistent state, check switches if is_in & mask: # both switches on? return status.ERROR, 'both switches on for element ' \ 'at %d m' % (i + self.first) return status.BUSY, 'elements moving' # HasTimeout will check for target reached return status.OK, 'idle' def doRead(self, maxage=0): is_in = self._attached_input_in.read(maxage) # extract the lowest set bit (element) for i in range(20 - self.first): if is_in & (1 << i): return i + self.first return 20 def doStart(self, target): # there are 18 bits for the collimation elements at 2..19 meters # bit 0 is the element at self.first, last bit is at 19m # move in all elements from 19m to target (20m = all open) bits = ((1 << (20 - target)) - 1) << (target - self.first) self._attached_output.start(bits) # without this bit, no outputs will be changed self._attached_sync_bit.start(1)
class Polarizer(Moveable): """Controls both the position of the polarizer and the spin flipper. """ valuetype = oneof(*POL_SETTINGS) hardware_access = False attached_devices = { 'switcher': Attach('polarizer in/out switch', Moveable), 'flipper': Attach('flipper', Moveable), } parameters = { 'values': Param('Possible values (for GUI)', internal=True, type=listof(str), default=POL_SETTINGS), 'switchervalues': Param('Possible values for the switcher (out, in)', type=tupleof(str, str), default=('ng', 'pol')), } parameter_overrides = { 'fmtstr': Override(default='%s'), 'unit': Override(mandatory=False, default=''), } def doRead(self, maxage=0): switcher_pos = self._attached_switcher.read(maxage) flipper_pos = self._attached_flipper.read(maxage) if switcher_pos == 'unknown' or flipper_pos == 'unknown': return 'unknown' if switcher_pos == self.switchervalues[0]: return 'out' # Polarizer is a transmission supermirror => without flipper we get # the "down" polarization. if flipper_pos == 'on': return 'up' return 'down' def doStart(self, target): switch_pos = self._attached_switcher.read(0) if target == 'out': if switch_pos != self.switchervalues[0]: self._attached_switcher.start(self.switchervalues[0]) self._attached_flipper.start('off') else: if switch_pos != self.switchervalues[1]: self._attached_switcher.start(self.switchervalues[1]) if target == 'up': self._attached_flipper.start('on') elif target == 'down': self._attached_flipper.start('off')
class NIAGControl(ControlDetector): """" A detector control class which implements the NIAG CCD counting features: - restart counting when the countrate was to low """ attached_devices = { 'rate_monitor': Attach('Monitor to check rate against', Readable), 'rate_threshold': Attach('Threshold defining the minimum acceptable ' 'rate', Moveable), 'exp_ok': Attach('Indication of sufficient exposure', Readable), } _triggerFinished = None _start_time = None _stopped = False def doStart(self): self._start_time = time.time() self._stopped = False ControlDetector.doStart(self) def doStop(self): self._stopped = True ControlDetector.doStop(self) def _testRate(self): mon = self._attached_rate_monitor.read(0) thres = self._attached_rate_threshold.read(0) exp_ok = self._attached_exp_ok.read(0) if isinstance(mon, list): mon = mon[0] if exp_ok != 1: session.log.info('%s, should be > %d uA, is %f uA', 'Restarting because of insuffient count rate', thres, mon) self.start() return False self._start_time = None return True def doIsCompleted(self): ret = ControlDetector.doIsCompleted(self) if ret and self._start_time and not self._stopped: return self._testRate() return ret def doRead(self, maxage=0): res = [self._attached_trigger.read(maxage)] for det in self._attached_slave_detectors: res = res.append(det.read(maxage)) return res
class HAMEG8131(EpicsMoveable): """ This class takes care of initialising the frequency generator, and switching it on or off """ attached_devices = { 'port': Attach('Port to talk directly to the device', EpicsCommandReply), 'freq': Attach('Flipper frequency', EpicsMoveable), 'amp': Attach('Flipper amplitude', EpicsMoveable), } valuetype = oneof('on', 'off') def doInit(self, mode): if mode == SIMULATION: return inicommands = ['RM1', 'RFI', 'OT0', 'SW0', 'SK0', 'CTM', 'VPP', 'AM0', 'SIN', 'OFS:0E+0', 'FRQ:2.534E+5', 'AMP:2.6E+0'] for com in inicommands: self._attached_port.execute(com) def doIsAllowed(self, target): if target == 'on': freq = self._attached_freq.read(0) amp = self._attached_amp.read(0) if freq < 1. or amp < .1: return False, 'Set frequency and amplitude first' return True, '' def doStart(self, pos): if pos == 'on': self._put_pv('writepv', 1) else: self._put_pv('writepv', 0) def doRead(self, maxage=0): val = self._get_pv('readpv') freq = self._attached_freq.read(maxage) amp = self._attached_amp.read(maxage) if val == 0 or freq < 1. or amp < .1: return 'off' return 'on' def doStatus(self, maxage=0): pos = self.doRead(maxage) if pos == self.target: return status.OK, 'Done' return status.BUSY, 'Moving'
class SH_Cylinder(Moveable): """PUMA specific device for the shutter cylinder.""" attached_devices = { 'io_ref': Attach('limit switches', Readable), 'io_air': Attach('air in the open/close direction', Moveable), 'io_pos': Attach('position stop, if in closed position, -1', Moveable), } parameters = { 'timedelay': Param('Waiting time for the movement', type=float, default=3, settable=True, mandatory=False, unit='s'), } def doStart(self, position): if position == self.read(0): return if self._checkAir() != 1: raise NicosError( self, 'No air! Please open the shutter with the ' 'button near the door.') self._attached_io_air.move(0) session.delay(self.timedelay) if self._attached_io_ref.read(0) != 1: raise NicosError(self, 'Cannot close the shutter!') if position != -1: self._attached_io_pos.move(position) session.delay(self.timedelay) self._attached_io_air.move(1) session.delay(self.timedelay) def doRead(self, maxage=0): if self._attached_io_ref.read(0) == 1: return -1 if self._attached_io_air.read(0) == 1: return self._attached_io_pos.read(0) def _checkAir(self): if self._attached_io_ref.read(0) == 1: self._attached_io_air.move(1) session.delay(self.timedelay) if self._attached_io_ref.read(0) == 1: return 0 return 1
class Shutter(HasTimeout, Moveable): """Controlling the shutter.""" valuetype = oneof('closed', 'open') parameters = { 'waittime': Param('Additional time to wait before open', settable=True, unit='s', default=0), } attached_devices = { 'output': Attach('output bits', Moveable), 'input': Attach('input bits', Readable), } parameter_overrides = { 'fmtstr': Override(default='%s'), 'timeout': Override(default=10), 'unit': Override(mandatory=False, default=''), } def doStatus(self, maxage=0): inputstatus = self._attached_input.read(maxage) if inputstatus == READ_OPEN: if self.target == 'open': return status.OK, 'idle' else: return status.WARN, 'open, but target=closed' elif inputstatus == READ_CLOSED: if self.target == 'closed': return status.OK, 'idle' else: return status.WARN, 'closed, but target=open' # HasTimeout will check for target reached return status.OK, 'idle' def doRead(self, maxage=0): inputstatus = self._attached_input.read(maxage) if inputstatus == READ_OPEN: return 'open' elif inputstatus == READ_CLOSED: return 'closed' return 'unknown' def doStart(self, target): session.delay(self.waittime) if target == 'open': self._attached_output.start(WRITE_OPEN) else: self._attached_output.start(WRITE_CLOSED)
class PGFilter(Moveable): """PUMA specific device for the PG filter.""" attached_devices = { 'io_status': Attach('status of the limit switches', Readable), 'io_set': Attach('output to set', Moveable), } valuetype = oneof('in', 'out') def doStart(self, position): try: if self.doStatus()[0] != status.OK: raise NicosError(self, 'filter returned wrong position') if position == self.read(0): return if position == 'in': self._attached_io_set.move(1) elif position == 'out': self._attached_io_set.move(0) else: # shouldn't happen... self.log.info('PG filter: illegal input') return session.delay(2) if self.doStatus()[0] == status.ERROR: raise NicosError( self, 'PG filter is not readable, please ' 'check device!') finally: self.log.info('PG filter: %s', self.read(0)) def doRead(self, maxage=0): result = self._attached_io_status.doRead(0) if result == 2: return 'in' elif result == 1: return 'out' else: raise NicosError(self, 'PG filter is not readable, check device!') def doStatus(self, maxage=0): s = self._attached_io_status.doRead(0) if s in [1, 2]: return (status.OK, 'idle') else: return (status.ERROR, 'filter is in error state')
class VSState(Moveable): """ This class switches the velocity selector on and off and checks if the thing can actually run """ attached_devices = { 'on': Attach('Writable I/O for switching on the VS', Moveable), 'off': Attach('Writable I/O for switching off the VS', Moveable), 'state': Attach('Readable I/O for the runable state of the VS', Readable), 'hand': Attach('Readable I/O for manual state VS', Readable), 'ready': Attach('Readable I/O for the ready state of the VS', Readable), } parameter_overrides = { 'unit': Override(mandatory=False), } _target = None def isAllowed(self, pos): if self._attached_ready.read(0) == 0: return False, 'VS not switched on' if self._attached_hand.read(0) == 1: return False, 'VS is in manual mode' if pos in ['on', 'off']: return True, '' return False, '%s not allowed, only on/off permitted' def doRead(self, maxage=0): if self._attached_state.read(maxage) == 1: return 'on' return 'off' def doStart(self, pos): self._target = pos if pos == 'on': self._attached_on.start(1) return self._attached_off.start(1) def doStatus(self, maxage=0): cur = self.read(maxage) if cur == self._target: self._attached_on.start(0) self._attached_off.start(0) return status.OK, 'Done' return status.BUSY, 'Waiting ...'
class Beamstop(Moveable): """Switcher for the beamstop position. This switches the beamstop in or out; the "out" value is determined by the current resolution preset. """ valuetype = oneof('out', 'in') attached_devices = { 'moveable': Attach('Beamstop motor', HasPrecision), 'resolution': Attach('Resolution device', Resolution), } parameters = { 'outpos': Param('Position for "out"'), } parameter_overrides = { 'unit': Override(mandatory=False, default=''), } def doRead(self, maxage=0): movpos = self._attached_moveable.read(maxage) if abs(movpos - self.outpos) <= self._attached_moveable.precision: return 'out' respos = self._attached_resolution.read(maxage) if respos != 'unknown': inpos = self._attached_resolution.presets[respos]['beamstop_x_in'] if abs(movpos - inpos) <= self._attached_moveable.precision: return 'in' return 'unknown' def doStatus(self, maxage=0): code, text = Moveable.doStatus(self, maxage) if code == status.OK and self.read(maxage) == 'unknown': return status.NOTREACHED, 'unknown position' return code, text def _getWaiters(self): return [self._attached_moveable] def doStart(self, pos): if pos == 'out': self._attached_moveable.start(self.outpos) return respos = self._attached_resolution.target if respos != 'unknown': inpos = self._attached_resolution.presets[respos]['beamstop_x_in'] self._attached_moveable.start(inpos) else: raise MoveError('no position for beamstop, resolution is unknown')