class RTE1104YScaleSetting(Moveable): parameters = { 'channel': Param('Input channel', type=intrange(1, 4), settable=False, default=1), } attached_devices = { 'io': Attach('Communication device', StringIO), 'regulator': Attach('regulator for the amplitude', Moveable, optional=True), } parameter_overrides = { 'unit': Override(mandatory=False, volatile=True), } valuetype = float def doReadUnit(self): if self._attached_regulator: return self._attached_regulator.unit return 'V' def doRead(self, maxage=0): # main value is target amplitude if self._attached_regulator: return self._attached_regulator.read(maxage) setting = float( self._attached_io.communicate('CHAN%d:SCAL?' % self.channel)) return setting * 10. / (2.2 * math.sqrt(2)) def doStart(self, value): setting = math.ceil(value * math.sqrt(2.) * 2.2) / 10. self._attached_io.writeLine('CHAN%d:SCAL %g' % (self.channel, setting)) if self._attached_regulator: self._attached_regulator.start(value) def doStatus(self, maxage=0): if self._attached_regulator: return self._attached_regulator.status(maxage) return status.OK, '' def doStop(self): if self._attached_regulator: self._attached_regulator.stop()
class ADKafkaImageDetector(KafkaSubscriber, ImageChannelMixin, PassiveChannel): """ Class for reading images from the stream associated with the ADPluginKafka """ attached_devices = { 'kafka_plugin': Attach('NICOS device for the ADPluginKafka', ADKafkaPlugin, optional=False) } def doPreinit(self, mode): broker = getattr(self._attached_kafka_plugin, 'broker') if not broker: raise Exception('Can\'t find broker address in ADPluginKafka') self._consumer = kafka.KafkaConsumer( bootstrap_servers=[broker], auto_offset_reset='latest' # start at latest offset ) # Settings for thread to fetch new message self._stoprequest = True self._updater_thread = None self._lastmessage = None def doInit(self, mode): topic = getattr(self._attached_kafka_plugin, 'topic') if not topic: raise Exception('Can\'t find topic in ADPluginKafka') self._consumer.subscribe([topic]) def doReadArray(self, quality=LIVE): deserializer = HistogramFlatbuffersDeserializer() try: data, _, _ = deserializer.decode(self._lastmessage[1]) except Exception as e: self.log.error(e) return [] self.readresult = [] return data.tolist() def new_messages_callback(self, messages): if not messages: return lastkey = sorted(list(messages.keys()))[-1] self._lastmessage = (lastkey, messages[lastkey]) def doStatus(self, maxage=0): st = self._attached_kafka_plugin.doStatus() if st[0] != status.OK: return st if not self._consumer: return status.ERROR, 'Broker failure' if not self._consumer.subscription(): return status.WARN, 'No topic subscribed' return status.OK, '' def valueInfo(self): return ()
class ShutterSink(DataSink): """ This class abuses the DataSink interface to open and close the ICON experiment shutters when a scan begins or ends. This is conditional on a ManualMove which decides if automatic shutter management is enabled or not. """ attached_devices = { 'shutter1': Attach('First shutter to open', Moveable), 'shutter2': Attach('Second shutter to open', Moveable), 'auto': Attach('Devices which knows if we are in automatic ' 'or manual mode', Readable), } handlerclass = ShutterHandler
class SelectorTilt(HasLimits, BaseSequencer): """Before the selector is allowed to tilt, the speed has to be reduced to a certain value. """ attached_devices = { 'selector': Attach('The selector speed', Moveable), 'motor': Attach('The tilt motor', Moveable), } parameters = { 'maxtiltspeed': Param('Maximum safe speed for tilting the selector', mandatory=True, type=int, unit='rpm'), } def _generateSequence(self, target): seq = [] if self._attached_selector.read(0) > self.maxtiltspeed: seq.insert( 0, SeqDev(self._attached_selector, self.maxtiltspeed, stoppable=True)) seq.append(SeqMethod(self, '_check_speed')) seq.append(SeqMethod(self._attached_motor, 'release')) seq.append(SeqDev(self._attached_motor, target, stoppable=True)) seq.append( SeqMethod(self._attached_motor, 'fix', 'only move this using %s' % self)) return seq def _check_speed(self): if self._attached_selector.read(0) > self.maxtiltspeed + 50: raise MoveError(self, 'selector not in safe speed range') def doRead(self, maxage=0): return self._attached_motor.read(maxage) def doIsAllowed(self, target): return self._attached_motor.isAllowed(target) def _getWaiters(self): return [self._attached_motor]
class QMesyDAQSink(FileSink): attached_devices = { 'image': Attach('Device to set the file name', Device), } parameter_overrides = { 'settypes': Override(default=[POINT]) }
class LVPower(Moveable): """Toni TOFTOF-type low-voltage power supplies.""" attached_devices = { 'bus': Attach('Toni communication bus', ToniBus), } parameters = { 'addr': Param('Bus address of the supply controller', type=intrange(0xF0, 0xFF), mandatory=True), 'temperature': Param('Temperature of the module', type=int, settable=False, volatile=True, unit='degC', fmtstr='%d'), } parameter_overrides = { 'unit': Override(mandatory=False, default=''), } valuetype = oneofdict({1: 'on', 0: 'off'}) def doRead(self, maxage=0): sval = self._attached_bus.communicate('S?', self.addr, expect_hex=2) return 'on' if sval >> 7 else 'off' def doReadTemperature(self): return self._attached_bus.communicate('T?', self.addr, expect_hex=2) def doStatus(self, maxage=0): sval = self._attached_bus.communicate('S?', self.addr, expect_hex=2) if sval in (0x0, 0x80): return status.OK, '' msg = [] if sval & 0x1: msg.append('undervoltage +5V') if sval & 0x2: msg.append('overvoltage +5V') if sval & 0x4: msg.append('undervoltage -5V') if sval & 0x8: msg.append('overvoltage -5V') if sval & 0x10: msg.append('undervoltage +12V') if sval & 0x20: msg.append('overvoltage +12V') return status.ERROR, ', '.join(msg) @requires(level=ADMIN) def doStart(self, target): self._attached_bus.communicate('P%d' % (target == 'on'), self.addr, expect_ok=True)
class AnalogValue(Readable): attached_devices = { 'iodev': Attach('IO Device', VSDIO), } parameters = { 'channel': Param('Channel for readout', type=oneof(*VSDIO._HW_AnalogChannels), settable=True, preinit=True), } parameter_overrides = { 'unit': Override(mandatory=False, volatile=True, settable=False), } def doReadUnit(self): _ofs, _scale, unit = \ self._attached_iodev._HW_AnalogChannels[self.channel] if unit == 'mA-foo': unit = 'mA' elif unit == 'V-foo': unit = 'V' return unit def doRead(self, maxage=0): ofs, scale, _unit = \ self._attached_iodev._HW_AnalogChannels[self.channel] # ofs is in Bytes, we need it in words! => /2 if _unit == 'mA-foo': raw = scale * self._attached_iodev._readU16(ofs // 2) self.log.debug('mA-foo %.2f', raw) # Work around bug in firmware if raw > 20.0: raw -= 615.37 self.log.debug('mA-foo %.2f', raw) # Tested against Multimeter (2018-08-07) raw /= 2.0 self.log.debug('mA-foo %.2f', raw) elif _unit == 'V-foo': raw = self._attached_iodev._readU16(ofs // 2) self.log.debug('V-foo %d', raw) # Work around bug in firmware if raw > 0x8000: raw -= 63536 self.log.debug('V-foo %d sign1', raw) self.log.debug('V-foo %d sign', raw) # Tested against Multimeter (2018-08-07) raw /= 2.0 self.log.debug('v-foo %.2f /2.0', raw) raw *= scale self.log.debug('v-foo %.2f scale', raw) else: raw = scale * self._attached_iodev._readU16(ofs // 2) return raw def doStatus(self, maxage=0): return status.OK, ''
class EulerSXTal(SXTalBase): attached_devices = { 'ttheta': Attach('two-theta device', Moveable), 'omega': Attach('omega device', Moveable), 'chi': Attach('chi device', Moveable), 'phi': Attach('phi device', Moveable), } def _extractPos(self, pos): return [ ('ttheta', np.rad2deg(2 * pos.theta)), ('omega', np.rad2deg(pos.omega)), ('chi', np.rad2deg(pos.chi)), ('phi', np.rad2deg(pos.phi)), ] def _convertPos(self, pos, wavelength=None): return pos.asE(wavelength) def _readPos(self, maxage=0): return PositionFactory('e', theta=self._attached_ttheta.read(maxage) / 2., omega=self._attached_omega.read(maxage), chi=self._attached_chi.read(maxage), phi=self._attached_phi.read(maxage)) def _createPos(self, ttheta, omega, chi, phi): return PositionFactory('e', theta=ttheta / 2., omega=omega, chi=chi, phi=phi) def getScanWidthFor(self, hkl): """Get scanwidth as ``sqrt(u + v *tan Theta + w * tan² Theta)``.""" th = session.experiment.sample.cell.Theta(hkl, self.wavelength) tan_th = np.tan(th) w2 = self.scan_uvw[0] + \ self.scan_uvw[1] * tan_th + \ self.scan_uvw[2] * (tan_th ** 2) if w2 > 0: return np.sqrt(w2) else: return -np.sqrt(-w2)
class HoveringAxis(SequencerMixin, Axis): """An axis that also controls air for airpads.""" attached_devices = { 'switch': Attach('The device used for switching air on and off', Moveable), } parameters = { 'startdelay': Param('Delay after switching on air', type=float, mandatory=True, unit='s'), 'stopdelay': Param('Delay before switching off air', type=float, mandatory=True, unit='s'), 'switchvalues': Param('(off, on) values to write to switch device', type=tupleof(anytype, anytype), default=(0, 1)), } hardware_access = True def _generateSequence(self, target): return [ SeqDev(self._attached_switch, self.switchvalues[1]), SeqSleep(self.startdelay), SeqCall(Axis.doStart, self, target), SeqCall(self._hw_wait), SeqSleep(self.stopdelay), SeqDev(self._attached_switch, self.switchvalues[0]), ] def _hw_wait(self): # overridden: query Axis status, not HoveringAxis status while Axis.doStatus(self, 0)[0] == status.BUSY: session.delay(self._base_loop_delay) def doStart(self, target): if self._seq_is_running(): self.stop() self.log.info('waiting for axis to stop...') self.wait() if abs(target - self.read()) < self.precision: return self._startSequence(self._generateSequence(target)) def doStop(self): # stop only the axis, but the sequence has to run through Axis.doStop(self) def doTime(self, start, end): return Axis.doTime(self, start, end) + self.startdelay + self.stopdelay
class BeamFocus(Moveable): attached_devices = { 'ellipse': Attach('output signal for ellipse', Moveable), 'collimator': Attach('output signal for collimator', Moveable), } parameter_overrides = { 'unit': Override(mandatory=False, default=''), 'fmtstr': Override(default='%s'), } valuetype = oneof('Ell', 'Col') hardware_access = False def doInit(self, mode): ell_state = self._attached_ellipse.read(0) col_state = self._attached_collimator.read(0) if [ell_state, col_state] == [1, 1]: self.reset() def doReset(self): self._attached_ellipse.move(0) self._attached_collimator.move(0) def doStart(self, pos): if pos != self.doRead(0): if pos == 'Ell': self._attached_collimator.move(0) self._attached_ellipse.move(1) elif pos == 'Col': self._attached_ellipse.move(0) self._attached_collimator.move(1) self._hw_wait() def doRead(self, maxage=0): ell = self._attached_ellipse.read(maxage) col = self._attached_collimator.read(maxage) if [ell, col] == [0, 1]: return 'Col' elif [ell, col] == [1, 0]: return 'Ell' return None
class Attenuator(HasTimeout, Moveable): """Controlling the attenuator.""" valuetype = oneof('out', 'in') 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 == OUT: if self.target == 'out': return status.OK, 'idle' else: return status.WARN, 'out, but target=in' elif inputstatus == IN: if self.target == 'in': return status.OK, 'idle' else: return status.WARN, 'in, but target=out' # HasTimeout will check for target reached return status.OK, 'idle' def doRead(self, maxage=0): inputstatus = self._attached_input.read(maxage) if inputstatus == OUT: return 'out' elif inputstatus == IN: return 'in' return 'unknown' def doStart(self, target): if target == 'out': self._attached_output.start(OUT) else: self._attached_output.start(IN)
class Attenuator(HasLimits, Moveable): """Attentuator with 3 elements having a transmission of 1/2, 1/4, and 1/16 """ attached_devices = { 'blades': Attach('The blade devices', Moveable, multiple=3), } parameters = { 'base': Param( 'Attenuating base (transmission = 1 / base ** n)', default=2, userparam=False, settable=False, ), } parameter_overrides = { 'unit': Override( default='%', mandatory=False, ), 'abslimits': Override(mandatory=False, default=(0, 100), settable=False), } def _attenuation(self, exp): return 100. * (1.0 - 1.0 / self.base**exp) def doRead(self, maxage=0): exp = 0 i = 1 for blade in self._attached_blades: if blade.read(maxage) == 'in': exp += i i *= 2 return self._attenuation(exp) def doStatus(self, maxage=0): return status.OK, '' def doStart(self, value): for pos in range(8): if value <= self._attenuation(pos) or pos == 7: # Move first in all needed blades into the beam to reduce the # activation and/or save the detector and then move out the not # needed ones for (idx, blade) in enumerate(self._attached_blades): if pos & (1 << idx): blade.maw('in') for (idx, blade) in enumerate(self._attached_blades): if not (pos & (1 << idx)): blade.maw('out') break
class LogoFeedBack(DigitalInput): """Device to simulate the LOGO feed back.""" attached_devices = { 'input': Attach('Digital input device', DigitalInput), } def doRead(self, maxage=0): v = self._attached_input.read(maxage) return sum((0x2 | min(1, ((1 << i) & v))) << (2 * i) for i in range(8))
class MuxMotor(Motor): """CARESS motor using the ST180 multiplexer.""" attached_devices = { 'mux': Attach('Multiplexer device to access the motor controller', MUX, optional=True, multiple=False), }
class DetectorZAxis(HasLimits, BaseSequencer): """Special device for the detector Z axis. Switches HV off before moving. """ hardware_access = False parameters = { 'precision': Param('Precision', volatile=True), } attached_devices = { 'motor': Attach('The raw motor', Moveable), 'hv': Attach('The HV switch', Moveable), } def doReadPrecision(self): return self._attached_motor.precision def doReadAbslimits(self): return self._attached_motor.abslimits def _generateSequence(self, target): seq = [] if self._attached_hv.read(0) != 'off': seq.append(SeqDev(self._attached_hv, 'off')) seq.append(SeqDev(self._attached_motor, target, stoppable=True)) return seq def doRead(self, maxage=None): return self._attached_motor.read(maxage) def doStatus(self, maxage=None): code, text = BaseSequencer.doStatus(self, maxage) if code == status.OK: text = self._attached_motor.status(maxage)[1] return code, text def doStart(self, target): if abs(self.read(0) - target) <= self.precision: return BaseSequencer.doStart(self, target)
class DetSwitcher(Moveable): """Switches the channel alias device between HRD and VHRD.""" attached_devices = { 'alias': Attach('the alias to switch', DeviceAlias), 'hrd': Attach('the HRD device', Measurable), 'vhrd': Attach('the VHRD device', Measurable), } _values = ['HRD', 'VHRD'] parameters = { 'values': Param('Possible values', type=listof(str), default=_values, settable=False, internal=True), } parameter_overrides = { 'unit': Override(default='', mandatory=False), } valuetype = oneof(*_values) def doRead(self, maxage=0): if self._attached_alias.alias == self._attached_hrd.name: return 'HRD' elif self._attached_alias.alias == self._attached_vhrd.name: return 'VHRD' return 'unknown' def doStatus(self, maxage=0): if self.doRead(maxage) == 'unknown': return status.WARN, '' return status.OK, '' def doStart(self, target): if target == 'HRD': self._attached_alias.alias = self._attached_hrd else: self._attached_alias.alias = self._attached_vhrd
class LensControl(HasTimeout, Moveable): """Control the 3 lenses.""" valuetype = intrange(0, 7) 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), } parameter_overrides = { 'fmtstr': Override(default='%d'), 'timeout': Override(default=10), 'unit': Override(mandatory=False, default=''), } 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(3): 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 lens ' \ '%d' % (i + 1) return status.BUSY, 'lenses moving' # HasTimeout will check for target reached return status.OK, 'idle' def doRead(self, maxage=0): return self._attached_input_in.read(maxage) def doStart(self, target): self._attached_output.start(target) # without this bit, no outputs will be changed self._attached_sync_bit.start(1)
class MiraXmlSink(ImageSink): handlerclass = MiraXmlHandler attached_devices = { 'timer': Attach('Timer readout', ActiveChannel), 'monitor': Attach('Monitor readout', ActiveChannel), 'mono': Attach('Monochromator device to read out', Monochromator), 'sampledet': Attach('Sample-detector distance readout', Readable), } parameter_overrides = { 'filenametemplate': Override(default=['mira_cas_%(pointcounter)08d.xml'], settable=False), } _format = None def isActiveForArray(self, arraydesc): # only for 2-D data return len(arraydesc.shape) == 2
class PumpstandPressure(Readable): attached_devices = { 'iodev': Attach('IO Device', PumpstandIO), } parameters = { 'chamber': Param('Chamber of pumpenstand', type=oneof('SFK', 'CB', 'SR'), mandatory=True, settable=False, userparam=False, default='SR'), 'limits': Param('Pump activation limits', type=limits, settable=True, volatile=True, chatty=True, unit='mbar'), } parameter_overrides = { 'unit': Override(default='mbar', mandatory=False), 'fmtstr': Override(default='%.1g'), } # # Nicos methods # def doReset(self): self._attached_iodev._HW_rawCommand('ackErr') def doRead(self, maxage=0): return getattr(self._attached_iodev, '_HW_%s_current_pressure' % self.chamber)() def doReadLimits(self): return (getattr(self._attached_iodev, '_HW_%s_turn_off_pressure' % self.chamber)(), getattr(self._attached_iodev, '_HW_%s_turn_on_pressure' % self.chamber)()) def doWriteLimits(self, limits): lower, upper = limits upper = clamp(upper, 1e-3, self.parallel_pumping) lower = clamp(lower, 1e-3, upper) self._attached_iodev._HW_rawCommand('set_%s_low' % self.chamber, int(lower * 1e6)) self._attached_iodev._HW_rawCommand('set_%s_high' % self.chamber, int(upper * 1e6)) return (lower, upper) _HW_Alarmbit = dict(CB=13, SR=14, SFK=15) def doStatus(self, maxage=0): alarms = self._attached_iodev._HW_readAlarms() alarmbit = self._HW_Alarmbit[self.chamber] if alarms & (1 << alarmbit): return status.ERROR, 'Sensor- oder Kabelfehler Messtube' return status.OK, ''
class CryopadPol(Moveable): """Controls the incoming or outgoing polarization direction.""" attached_devices = { 'cryopad': Attach('Underlying Cryopad device', BaseCryopad) } parameters = { 'side': Param('Which side of the instrument is this device?', type=oneof('in', 'out'), mandatory=True), } parameter_overrides = { 'unit': Override(mandatory=False, default=''), } valuetype = oneof('+x', '-x', '+y', '-y', '+z', '-z') def doRead(self, maxage=0): # NOTE: for now, we just return the latest target because it became # necessary to introduce corrections to nutator angles that cannot yet # be included reliably in the calculations, and would change the readout # here to "unknown". return self.target # cppos = self._attached_cryopad.read(maxage) # theta, chi = cppos[0:2] if self.side == 'in' else cppos[2:4] # for (pos, (goaltheta, goalchi)) in calc.DIRECTIONS.items(): # # fix chi = -179.5, goalchi = 180 situation # diffchi = abs(chi - goalchi) # chiok = diffchi < 0.5 or 359.5 < diffchi < 360.5 # # XXX: make precision configurable # if abs(theta - goaltheta) < 0.5 and chiok: # return pos # return 'unknown' def doStatus(self, maxage=0): st, text = multiStatus(self._adevs) if st == status.BUSY: return st, text if self.read(maxage) == 'unknown': return status.NOTREACHED, 'unknown polarization setting' return status.OK, 'idle' def doStart(self, pos): theta_chi = calc.DIRECTIONS[pos] cppos = self._attached_cryopad.target or (0, 0, 0, 0) if self.side == 'in': target = tuple(theta_chi) + cppos[2:4] else: target = cppos[0:2] + tuple(theta_chi) self._attached_cryopad.start(target)
class RTE1104TimescaleSetting(Moveable): attached_devices = { 'io': Attach('Communication device', StringIO), 'freqgen': Attach('frequency generator', Moveable, optional=True), } parameters = { 'timescale': Param('Time scale setting', type=float, settable=False, userparam=False, default=10., category='general'), } parameter_overrides = { 'unit': Override(mandatory=False, default='Hz'), } valuetype = float def doRead(self, maxage=0): # main value is freq! if self._attached_freqgen: return self._attached_freqgen.read(maxage) return self.timescale / float( self._attached_io.communicate('TIM:SCAL?' % self.channel)) def doStart(self, target): self._attached_io.writeLine('TIM:SCAL %g' % (self.timescale / float(target))) if self._attached_freqgen: self._attached_freqgen.start(target) def doStatus(self, maxage=0): if self._attached_freqgen: return self._attached_freqgen.status(maxage) return status.OK, ''
class Shutter(Moveable): """TOFTOF Shutter Control.""" attached_devices = { 'open': Attach('Shutter open button device', DigitalOutput), 'close': Attach('Shutter close button device', DigitalOutput), 'status': Attach('Shutter status device', DigitalOutput), } parameter_overrides = { 'unit': Override(mandatory=False), 'fmtstr': Override(default='%s'), } valuetype = oneofdict({0: 'closed', 1: 'open'}) def doStart(self, target): if target == 'open': self._attached_open.start(1) session.delay(0.01) self._attached_open.start(0) else: self._attached_close.start(1) session.delay(0.01) self._attached_close.start(0) def doStop(self): self.log.info( 'note: shutter collimator does not use stop() anymore, ' 'use move(%s, "closed")', self) def doRead(self, maxage=0): ret = self._attached_status.read(maxage) if ret == 1: return 'closed' else: return 'open' def doStatus(self, maxage=0): return status.OK, ''
class SamplePusher(Moveable): """Move the sample up/down inside the sample changer device.""" valuetype = oneof('down', 'up') attached_devices = { 'actuator': Attach('Actuator to perform the switch', Moveable), 'sensort': Attach('Sensor at top of the tube.', Readable), 'sensorl': Attach('Sensor at down of the tube', Readable), } parameter_overrides = { 'unit': Override(default=''), 'fmtstr': Override(default='%s'), } def doInit(self, mode): self._target_sens = None def doStart(self, target): self._attached_actuator.move(target) if target == 'up': self._target_sens = self._attached_sensort elif target == 'down': self._target_sens = self._attached_sensorl def doStatus(self, maxage=0): # it is a local object so poller gives wrong state here but maw works if self._target_sens: if self._target_sens.read(maxage) == 0: return status.BUSY, 'moving' elif self._target_sens.read(maxage) == 1: self._target_sens = None return status.OK, 'idle' def doRead(self, maxage=0): if self._attached_sensort.read(maxage): return 'up' elif self._attached_sensorl.read(maxage): return 'down'
class Valve(Moveable): """Control element for 8 valves.""" attached_devices = { 'bus': Attach('Toni communication bus', ModBus), } parameters = { 'addr': Param('Bus address of the valve control', type=int, mandatory=True), 'channel': Param('Channel of the valve', type=intrange(0, 7), mandatory=True), 'states': Param('Names for the closed/open states', type=listof(str), default=['off', 'on']), 'waittime': Param('Time to wait after switching', type=float, unit='s', default=4), } parameter_overrides = { 'unit': Override(mandatory=False), } _started = 0 def doInit(self, mode): if len(self.states) != 2: raise ConfigurationError( self, 'Valve states must be a list of ' 'two strings for closed/open state') self.valuetype = oneof(*self.states) def doStart(self, value): value = self.states.index(value) self._hw_wait() msg = '%s=%02x' % (value and 'O' or 'C', 1 << self.channel) self._attached_bus.communicate(msg, self.addr, expect_ok=True) self._started = currenttime() def doRead(self, maxage=0): ret = self._attached_bus.communicate('R?', self.addr, expect_hex=2) return self.states[bool(ret & (1 << self.channel))] def doStatus(self, maxage=0): ret = self._attached_bus.communicate('I?', self.addr, expect_hex=2) if ret != 0: return status.BUSY, 'busy' if self._started and self._started + self.waittime < currenttime(): return status.BUSY, 'waiting' else: return status.OK, 'idle'
class WutDiff(Readable): attached_devices = { 'dev1': Attach('1st Device', Readable), 'dev2': Attach('2nd Device', Readable), } parameter_overrides = { 'unit': Override(mandatory=False), 'pollinterval': Override(default=60), 'maxage': Override(default=125), } def doReadUnit(self): return self._attached_dev1.unit def doRead(self, maxage=0): return self._attached_dev1.doRead(maxage) - \ self._attached_dev2.doRead(maxage) def doStatus(self, maxage=0): return status.OK, ''
class Leckmon(Readable): """Water supply leak monitor.""" attached_devices = { 'bus': Attach('Toni communication bus', ModBus), } parameters = { 'addr': Param('Bus address of monitor', type=int, mandatory=True), } def doRead(self, maxage=0): return self._attached_bus.communicate('S?', self.addr)
class SampleChanger(IsController, BaseSequencer): """The PGAA sample changer device.""" attached_devices = { 'motor': Attach('Stage rotation', Moveable), 'push': Attach('Moving sample to rotation stage', Moveable), } parameter_overrides = { 'unit': Override(default=''), 'fmtstr': Override(default='%.f'), } def isAdevTargetAllowed(self, dev, target): if dev == self._attached_motor: if not self._attached_push._attached_sensort.read(0): return False, '"push" is not in top position or moving' elif dev == self._attached_push: if self._attached_motor.status(0)[0] == status.BUSY: return False, 'motor moving' if self._attached_motor.read(0) not in (1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16.): return False, 'invalid motor position' return True, '' def _generateSequence(self, target): seq = [] if target != self.doRead(0): seq.append(SeqDev(self._attached_push, 'up', stoppable=False)) seq.append(SeqSleep(2)) seq.append(SeqSampleMotor(self._attached_motor, target, stoppable=False)) seq.append(SeqSleep(2)) seq.append(SeqDev(self._attached_push, 'down', stoppable=False)) return seq def doRead(self, maxage=0): return self._attached_motor.read(maxage)
class SelectorSpeed(HasLimits, HasPrecision, Moveable): """Control selector speed via PLC I/O devices.""" attached_devices = { 'valid': Attach('Output for the "valid" bit', Moveable), 'speed': Attach('I/O for current speed and its setpoint', Moveable), 'status': Attach('Input for reading the status', Readable), } def _getWaiters(self): return [self._attached_speed] def doRead(self, maxage=0): return self._attached_speed.read(maxage) def doStatus(self, maxage=0): stbits = self._attached_status.read(maxage) if not stbits & 1: return status.WARN, 'local mode' if not stbits & 2: if self._attached_speed.read(maxage) < 0.1: return status.WARN, 'off' return status.BUSY, 'moving' return status.OK, '' def doStart(self, pos): # do not start moving if already there, since the stability check in # the SPS always waits if pos == self.target and abs(self.read(0) - pos) < self.precision: return stbits = self._attached_status.read(0) if not stbits & 1: raise MoveError(self, 'selector is in local mode') # valid bit needs a rising edge self._attached_valid.move(0) self._attached_speed.maw(pos) time.sleep(0.2) self._attached_valid.move(1) time.sleep(0.2)
class AmorLogicalMotor(Motor): """ Class to represent the logical motor in AMOR. This motor has a type which can be one of the ath(analyzer theta), m2t(monochormator two theta) or s2t(sample two theta). """ parameters = { 'motortype': Param('Type of motor ath/m2t/s2t', type=oneof(*motortypes), mandatory=True), } parameter_overrides = { 'unit': Override(mandatory=False, default='degree'), 'target': Override(volatile=True), 'abslimits': Override(mandatory=False, default=(-3.0, 3.0)), 'userlimits': Override(mandatory=False, default=(-3.0, 3.0)) } attached_devices = { 'controller': Attach('Controller for the logical motors', AmorLogicalMotorHandler) } def doInit(self, mode): self._attached_controller.register(self.motortype, self) def doRead(self, maxage=0): return self._attached_controller.doRead(maxage)[self.motortype] def doReadTarget(self): return self._getFromCache('target', self.doRead) def doStatus(self, maxage=0): # Check for error and warning in the dependent devices return self._attached_controller.doStatus(maxage) def doIsAllowed(self, pos): return self._attached_controller.doIsAllowed({self.motortype: pos}) def doIsCompleted(self): return self._attached_controller.doIsCompleted() def doStart(self, pos): self._attached_controller.doStart({self.motortype: pos}) def doStop(self): if self.status(0)[0] == status.BUSY: self._attached_controller.stop() # Reset the target for this motor self._setROParam('target', self.doRead(0))
class FPLCRate(AnalogInput): """Forward the current detector countrate to the FPLC.""" attached_devices = { 'rate': Attach('device to read countrate', Readable), } def doStatus(self, maxage=0): return status.OK, '' def doRead(self, maxage=0): rate = self._attached_rate.read(maxage)[1] self._dev.value = rate return rate