class Resolution(Readable): valuetype = (float, float) attached_devices = { 'detector': Attach('Detector device with size information', Detector), 'beamstop': Attach('Beam stop device with size information', BeamStop), 'detpos': Attach('Detector position device', Readable), 'wavelength': Attach('Wavelength device', Readable), } parameter_overrides = { 'unit': Override(mandatory=False, volatile=True), 'fmtstr': Override(default='%3.f - %3.f'), } def doRead(self, maxage=0): bs = self._attached_beamstop return qrange(self._attached_wavelength.read(maxage), self._attached_detpos.read(0), bs.slots[bs.shape][1][0] / 2., self._attached_detector.size[0] / 2.) def doReadUnit(self): unit = self._attached_wavelength.unit return '%s-1' % unit
class MasterSlaveMotor(Moveable): """Combined master slave motor with possibility to apply a scale to the slave motor.""" attached_devices = { "master": Attach("Master motor controlling the movement", Moveable), "slave": Attach("Slave motor following master motor movement", Moveable), } parameters = { "scale": Param("Factor applied to master target position as slave " "position", type=float, default=1), } parameter_overrides = { "unit": Override(mandatory=False), "fmtstr": Override(default="%.3f %.3f"), } def _slavePos(self, pos): return self.scale * pos def doRead(self, maxage=0): return [ self._attached_master.read(maxage), self._attached_slave.read(maxage) ] def doStart(self, pos): self._attached_master.move(pos) self._attached_slave.move(self._slavePos(pos)) def doIsAllowed(self, pos): faultmsgs = [] messages = [] for dev in [self._attached_master, self._attached_slave]: allowed, msg = dev.isAllowed(pos) msg = dev.name + ': ' + msg messages += [msg] if not allowed: faultmsgs += [msg] if faultmsgs: return False, ', '.join(faultmsgs) return True, ', '.join(messages) def doReadUnit(self): return self._attached_master.unit def valueInfo(self): return Value(self._attached_master.name, unit=self.unit, fmtstr=self._attached_master.fmtstr), \ Value(self._attached_slave.name, unit=self.unit, fmtstr=self._attached_slave.fmtstr)
class FocusPoint(HasLimits, Moveable): attached_devices = { 'table': Attach('table', Moveable), 'pivot': Attach('pivot', Readable), } parameters = { 'gisansdistance': Param('GISANS distance', type=floatrange(0, None), default=10700), } parameter_overrides = { 'abslimits': Override(mandatory=True, volatile=False), } def moveToFocus(self): self._attached_table.move(self._calculation()) def doRead(self, maxage=0): return self._attached_table.read(maxage) def doStart(self, pos): # self.moveToFocus() or move table self._attached_table.move(pos) def _calculation(self, pivot=None): if pivot is None: pivot = self._attached_pivot.read(0) focus = self.gisansdistance - pivot * self._attached_pivot.grid self.log.debug('FocusPoint _calculation focus %f pivot %d', focus, pivot) return focus def doStatus(self, maxage=0): state = self._attached_table.status(maxage) if state[0] != status.OK: return state table = self._attached_table.read() focus = self._calculation() precision = 0 if hasattr(self._attached_table, '_attached_device'): precision = self._attached_table._attached_device.precision elif hasattr(self._attached_table, 'precision'): precision = self._attached_table.precision else: precision = 0 text = 'focus' if abs(table - focus) <= precision else state[1] return status.OK, text
class SkewRead(Readable): """Device having two axes and an inclination. The position is the mid between the one and two device. """ attached_devices = { 'one': Attach('readable device 1', Readable), 'two': Attach('readable device 2', Readable), } def _read_devices(self, maxage=0): return [d.read(maxage) for d in self._adevs.values()] def doRead(self, maxage=0): return sum(self._read_devices(maxage)) / 2.
class DetectorDistance(Readable): """Calculate detector distance based on the detector tubes position""" attached_devices = { 'detectubes': Attach('Pilatus detector tubes', Readable, multiple=4) } parameters = { 'offset': Param('Minimum distance between Pilatus and sample', type=int, settable=True), 'tubelength': Param('List of tube length', type=listof(int), settable=False, default=[450, 450, 900, 900]), } hardware_access = False def doInit(self, mode): self.log.debug('Detector distance init') self.read() def doRead(self, maxage=0): distance = 0 for tube, l in zip(self._attached_detectubes, self.tubelength): # tubes can only be set in correct sequence if tube.read(maxage) != 'up': break distance += l return self.offset + distance
class ChopperBase(DeviceMixinBase): attached_devices = { 'comm': Attach('Communication device', StringIO), } def _read_controller(self, mvalue): # TODO this has to be fix somehow if hasattr(self, 'chopper'): what = mvalue % self.chopper else: what = mvalue # self.log.debug('_read_controller what: %s', what) res = self._attached_comm.communicate(what) res = res.replace('\x06', '') # self.log.debug('_read_controller res for %s: %s', what, res) return res def _read_base(self, what): res = self._attached_comm.communicate(what) res = res.replace('\x06', '') return res def _write_controller(self, mvalue, *values): # TODO: fix formatting for single values and lists # TODO: this has to be fix somehow if hasattr(self, 'chopper'): what = mvalue % ((self.chopper, ) + values) else: what = mvalue % (values) self.log.debug('_write_controller what: %s', what) self._attached_comm.writeLine(what)
class RateImageChannel(BaseImageChannel): """Subclass of the Tango image channel that automatically returns the sum of all counts and the momentary count rate as scalar values. """ attached_devices = { 'timer': Attach('The timer channel', Measurable), } def doInit(self, mode): BaseImageChannel.doInit(self, mode) self._rate_data = [0, 0] def doReadArray(self, quality): narray = BaseImageChannel.doReadArray(self, quality) seconds = self._attached_timer.read(0)[0] cts = narray.sum() rate = calculateRate(self._rate_data, quality, cts, seconds) self.readresult = [cts, rate] return narray def valueInfo(self): return (Value(name=self.name + ' (total)', type='counter', fmtstr='%d', errors='sqrt', unit='cts'), Value(name=self.name + ' (rate)', type='monitor', fmtstr='%.1f', unit='cps'),)
class HTFTemperatureController(TemperatureController): attached_devices = { 'maxheater': Attach('Maximum heater output device', AnalogOutput), } parameters = { 'maxheateroutput': Param('Maximum heater output', type=floatrange(0, 100), userparam=True, settable=True, volatile=True, unit='%'), 'sensortype': Param('Currently used sensor type', type=str, userparam=True, settable=False, volatile=True, mandatory=False), } def doWriteMaxheateroutput(self, value): self._attached_maxheater.move(value) def doReadMaxheateroutput(self): return self._attached_maxheater.read(0) def doReadSensortype(self): return self._dev.sensortype
class TubeAngle(HasLimits, Moveable): """Angle of the tube controlled by the yoke.""" attached_devices = { 'yoke': Attach('Yoke device', Moveable), } parameters = { 'yokepos': Param('Position of yoke from pivot point', type=floatrange(0, 20000), unit='mm', default=11000), } parameter_overrides = { 'abslimits': Override(mandatory=False, volatile=True), 'unit': Override(mandatory=False, default='deg'), } def doRead(self, maxage=0): return degrees(atan2(self._attached_yoke.read(maxage), self.yokepos)) def doStart(self, target): self._attached_yoke.move(tan(radians(target)) * self.yokepos) def doReadAbslimits(self): yokelimits = self._attached_yoke.userlimits return (degrees(atan2(yokelimits[0], self.yokepos)), degrees(atan2(yokelimits[1], self.yokepos)))
class SkewMotor(Motor): """Device moving by using two axes and having a fixed inclination. Both axis have a fixed inclination given by the ``skew`` parameter. The position of the devices is given for the middle between both axis. The ``motor_1`` device has always the smaller position value than the ``motor_2`` device. pos(motor_1) + skew / 2 == pos == pos(motor_2) - skew / 2. """ attached_devices = { 'motor_1': Attach('moving motor, 1', Moveable), 'motor_2': Attach('moving motor, 2', Moveable), } parameters = { 'skew': Param('Skewness of hardware, difference between both motors', type=floatrange(0.), default=0., settable=True, unit='main'), } def _read_motors(self, maxage=0): return self._attached_motor_1.read(maxage), \ self._attached_motor_2.read(maxage) def doRead(self, maxage=0): return sum(self._read_motors(maxage)) / 2. def doIsAtTarget(self, pos): if self.target is None: return True if not self._attached_motor_1.isAtTarget(pos - self.skew / 2.) or \ not self._attached_motor_2.isAtTarget(pos + self.skew / 2.): return False m1, m2 = self._read_motors() self.log.debug('%.3f, %.3f, %.3f, %.3f', m1, m2, (m1 + self.skew / 2.), (m2 - self.skew / 2.)) return abs(m1 - m2 + self.skew) <= self.precision def doStart(self, target): self._attached_motor_1.move(target - self.skew / 2.) self._attached_motor_2.move(target + self.skew / 2.)
class NOKPosition(PolynomFit, Coder): """Device to read the current Position of a NOK. The Position is determined by a ratiometric measurement between two analogue voltages measured with i7000 modules via taco. As safety measure, the reference voltage obtained is checked to be in some configurable limits. """ attached_devices = { 'measure': Attach('Sensing Device (Poti)', Readable), 'reference': Attach('Reference Device', Readable), } parameters = { 'length': Param('Length... ????', type=float, mandatory=False), # fun stuff, not really needed.... 'serial': Param('Serial number', type=int, mandatory=False), } parameter_overrides = { 'fmtstr': Override(default='%.3f'), 'unit': Override(default='mm', mandatory=False), } def doReset(self): multiReset(self._adevs) def doRead(self, maxage=0): """Read basically two (scaled) voltages. - value from the poti (times a sign correction for top mounted potis) - ref from a reference voltage, scaled by 2 (Why???) Then it calculates the ratio poti / ref. Then this is put into a correcting polynom of at least first order Result is then offset + mul * <previously calculated value> """ poti = self._attached_measure.read(maxage) ref = self._attached_reference.read(maxage) self.log.debug('Poti vs. Reference value: %f / %f', poti, ref) # apply simple scaling return self._fit(poti / ref)
class DNSDetector(Detector): """Detector supporting different presets for spin flipper on or off.""" attached_devices = { 'flipper': Attach('Spin flipper device which will be read out ' 'with respect to setting presets.', BaseFlipper), } def _getWaiters(self): waiters = self._adevs.copy() del waiters['flipper'] return waiters def doTime(self, preset): if P_TIME in preset: return preset[P_TIME] elif P_TIME_SF in preset and self._attached_flipper.read() == ON: return preset[P_TIME_SF] elif P_TIME_NSF in preset and self._attached_flipper.read() == OFF: return preset[P_TIME_NSF] return 0 # no preset that we can estimate found def presetInfo(self): presets = Detector.presetInfo(self) presets.update((P_TIME_SF, P_TIME_NSF, P_MON_SF, P_MON_NSF)) return presets def doSetPreset(self, **preset): new_preset = preset if P_MON_SF in preset and P_MON_NSF in preset: if self._attached_flipper.read() == ON: m = preset[P_MON_SF] else: m = preset[P_MON_NSF] new_preset = {P_MON: m} elif P_MON_SF in preset or P_MON_NSF in preset: self.log.warning('Incorrect preset setting. Specify either both ' '%s and %s or only %s.', P_MON_SF, P_MON_NSF, P_MON) return elif P_TIME_SF in preset and P_TIME_NSF in preset: if self._attached_flipper.read() == ON: t = preset[P_TIME_SF] else: t = preset[P_TIME_NSF] new_preset = {P_TIME: t} elif P_TIME_SF in preset or P_TIME_NSF in preset: self.log.warning('Incorrect preset setting. Specify either both ' '%s and %s or only %s.', P_TIME_SF, P_TIME_NSF, P_TIME) return elif P_MON in preset: new_preset = {P_MON: preset[P_MON]} elif P_TIME in preset: new_preset = {P_TIME: preset[P_TIME]} Detector.doSetPreset(self, **new_preset)
class Timer(VirtualTimer): """Timer starts detector via digital IO.""" attached_devices = { 'digitalio': Attach('Timer channel', Moveable), } def _counting(self): self._attached_digitalio.move(1) VirtualTimer._counting(self) self._attached_digitalio.move(0)
class ReadonlySwitcher(MappedReadable): """Same as the `Switcher`, but for read-only underlying devices.""" attached_devices = { 'readable': Attach('The continuous device which is read', Readable), } parameters = { 'precision': Param('Precision for comparison', type=floatrange(0), default=0), } parameter_overrides = { 'fallback': Override(userparam=False, type=none_or(str), mandatory=False), } hardware_access = False def _readRaw(self, maxage=0): return self._attached_readable.read(maxage) def _mapReadValue(self, pos): prec = self.precision for name, value in iteritems(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 raise PositionError(self, 'unknown position of %s' % self._attached_readable) def doStatus(self, maxage=0): # if the underlying device is moving or in error state, # reflect its status move_status = self._attached_readable.status(maxage) if move_status[0] not in (status.OK, status.WARN): return move_status # otherwise, we have to check if we are at a known position, # and otherwise return an error status try: if self.read(maxage) == self.fallback: return status.NOTREACHED, 'unconfigured position of %s or '\ 'still moving' % self._attached_readable except PositionError as e: return status.NOTREACHED, str(e) return status.OK, '' def doReset(self): self._attached_readable.reset()
class CollimatorLoverD(Readable): """Special device related to the L over d relation.""" attached_devices = { 'l': Attach('Distance device', Readable), 'd': Attach('Pinhole', Readable), } def doInit(self, mode): if self._attached_l.unit != self._attached_d.unit: raise ConfigurationError( self, 'different units for L and d ' '(%s vs %s)' % (self._attached_l.unit, self._attached_d.unit)) def doRead(self, maxage=0): try: ret = float(self._attached_l.read(maxage)) / \ float(self._attached_d.read(maxage)) except ValueError: ret = 0 return ret
class BeamStopDevice(Readable): attached_devices = { 'att': Attach('VSD device', Readable), } def doRead(self, maxage=0): return self._attached_att.read(maxage) def doStatus(self, maxage=0): if self._attached_att.read(maxage) < 0.01: return status.ERROR, 'VSD disconected' return status.OK, ''
class RealFlightPath(Readable): attached_devices = { 'table': Attach('port to read real table', Readable), 'pivot': Attach('port to read real pivot', Readable), } parameter_overrides = { 'unit': Override(volatile=True, mandatory=False), } def doRead(self, maxage=0): table = self._attached_table.read(maxage) pivot = self._attached_pivot.read(maxage) self.log.debug('table=%s, pivot=%s', table, pivot) # in meter D = (table + pivot * self._attached_pivot.grid + pre_sample_path) / 1e3 self.log.debug('D=%.2f %s', D, self.unit) return D def doReadUnit(self): return 'm'
class GeometricBlur(Readable): """Special device to calculate geometric blur. Calculated from collimation and sample to detector distance.""" attached_devices = { 'l': Attach('Distance device', Readable), 'd': Attach('Pinhole', Readable), 'sdd': Attach('Sample Detector Distance', Readable), } parameter_overrides = { 'unit': Override(volatile=True, mandatory=False), } def doInit(self, mode): units = set(d.unit for d in self._adevs.values()) if len(units) != 1: raise ConfigurationError( self, 'different units for L, d and sdd ' '(%s vs %s vs %s)' % (self._attached_l.unit, self._attached_d.unit, self._attached_sdd.unit)) if 'mm' not in units: raise ConfigurationError( self, "attached devices units have to be " "'mm'") def doRead(self, maxage=0): try: ret = float(self._attached_sdd.read(maxage)) * \ float(self._attached_d.read(maxage)) / \ float(self._attached_l.read(maxage)) return 1000 * ret # convert to um except ValueError: ret = 0 return ret def doReadUnit(self): return 'um'
class ArmController(IsController, Device): parameters = { 'minangle': Param('Minimum angle between two arms', type=floatrange(0, None), settable=False, userparam=False, default=50.), } attached_devices = { 'arm1': Attach('Arm 1 device', Moveable), 'arm2': Attach('Arm 2 device', Moveable), } parameter_overrides = { 'lowlevel': Override(default=True), } def isAdevTargetAllowed(self, adev, adevtarget): self.log.debug('%s: %s', adev, adevtarget) if adev == self._attached_arm1: target = self._attached_arm2.target if target is None: target = self._attached_arm2.read(0) absdiff = target - adevtarget else: target = self._attached_arm1.target if target is None: target = self._attached_arm1.read(0) absdiff = adevtarget - target if absdiff < 0: return False, 'Arms will cross.' dist = abs(absdiff) if dist >= self.minangle: return True, '' return False, 'Arms become too close to each other: %.3f deg, min. ' \ 'dist is %.3f' % (dist, self.minangle)
class Resolution(Readable): """Calculate the wavelength resolution of the whole instrument. The chopper controller device is used to detect the real and virtual position of the second disc (chopper2). """ attached_devices = { 'chopper': Attach('chopper controller device', Readable), 'flightpath': Attach('Read the real flightpath', Readable), } parameter_overrides = { 'unit': Override(volatile=True, mandatory=False), } def doRead(self, maxage=0): return chopper_resolution( self._attached_chopper.target['chopper2_pos'], self._attached_flightpath.read(maxage)) def doReadUnit(self): return '%'
class Base(Readable): valuetype = str attached_devices = { 'comm': Attach('Communication device', StringIO), } def doStatus(self, maxage=0): return status.OK, '' def doRead(self, maxage=0): return self._attached_comm.communicate('read')
class ChopperDisc1(ChopperDisc): attached_devices = { 'discs': Attach('slave choppers', ChopperDisc, multiple=5), } def doStart(self, target): for dev in self._attached_discs: dev.start(target) ChopperDisc.doStart(self, target) def _getWaiters(self): return []
class NarzissSpin(BaseSequencer): attached_devices = { 'pom': Attach('Polariser rotation', Moveable), 'pmag': Attach('Polariser magnet', Moveable), } valuetype = oneof('+', '-', '0', 'undefined') def _generateSequence(self, target): seq = [] if target == '+': seq.append((SeqDev(self._attached_pom, 0.8), SeqDev(self._attached_pmag, -2.5))) seq.append(SeqSleep(4.)) seq.append(SeqDev(self._attached_pmag, .15)) elif target == '-': seq.append((SeqDev(self._attached_pom, 0.8), SeqDev(self._attached_pmag, 2.5))) seq.append(SeqSleep(4.)) seq.append(SeqDev(self._attached_pmag, 1.)) elif target == '0': seq.append((SeqDev(self._attached_pom, 3), SeqDev(self._attached_pmag, 0))) else: raise ProgrammingError('Invalid value requested') return seq def doRead(self, maxage=0): val = self._attached_pmag.read(maxage) if abs(val - .15) < .02: return '+' if abs(val - 1.) < .05: return '-' if abs(val - 0) < .05: return '0' return 'undefined'
class MotorEncoderDifference(Readable): attached_devices = { 'motor': Attach('moving motor', Moveable), 'analog': Attach('analog encoder maybe poti', Readable), } parameters = { 'absolute': Param('Value is absolute or signed.', type=bool, settable=True, default=True), } def doRead(self, maxage=0): dif = self._attached_analog.read(maxage) - \ self._attached_motor.read(maxage) return abs(dif) if self.absolute else dif def doStatus(self, maxage=0): return status.OK, ''
class AnalogEncoder(PolynomFit, Readable): attached_devices = { 'device': Attach('Sensing device (poti etc)', Readable), } def doRead(self, maxage=0): """Return a read analogue signal corrected by a polynom. A correcting polynom of at least first order is used. Result is then offset + mul * <previously calculated value> """ return self._fit(self._attached_device.read(maxage))
class ListmodeSink(FileSink): """Writer for the list mode files via QMesyDAQ itself.""" attached_devices = { 'image': Attach('Image device to set the file name', Image), } parameter_overrides = { 'settypes': Override(default=[POINT]), 'filenametemplate': Override(mandatory=False, userparam=False, default=['D%(pointcounter)07d.mdat']), } handlerclass = ListmodeSinkHandler
class AnalogCalc(Coder): attached_devices = { 'device1': Attach('first value for calculation', Readable), 'device2': Attach('second value for calculation', Readable), } parameters = { 'calc': Param('choose the calculation', type=oneof('mul', 'div', 'add', 'dif'), settable=False, mandatory=True, default='div'), } def doRead(self, maxage=0): """Return a read analogue signal corrected by a polynom. A correcting polynom of at least first order is used. Result is then offset + mul * <previously calculated value> """ value1 = self._attached_device1.read(maxage) value2 = self._attached_device2.read(maxage) self.log.info('value 1: %f 2: %f', value1, value2) if self.calc == 'mul': result = value1 * value2 elif self.calc == 'div': result = value1 / value2 elif self.calc == 'add': result = value1 + value2 elif self.calc == 'dif': result = value1 - value2 self.log.debug('final result: %f', result) return result
class TriState(CanDisable, Readable): attached_devices = { 'port': Attach('port to read the real number', Readable), } _enabled = True def doRead(self, maxage=0): self.log.debug('mode=%s' % self._enabled) if self._enabled: return self._attached_port.read(maxage) return 0 def doEnable(self, on): self._enabled = on
class DOFlipper(NamedDigitalOutput, Waitable): """Flipper controlled via one digital output monitoring power supplies status.""" attached_devices = { "powersupplies": Attach("Monitored power supplies", Readable, multiple=True), } def doStatus(self, maxage=0): tangoState, tangoStatus = NamedDigitalOutput.doStatus(self, maxage) state, status = Waitable.doStatus(self, maxage) if tangoState > state: state = tangoState status = tangoStatus + ', ' + status return state, status
class PollMotor(Motor): attached_devices = { "polldevs": Attach("Devices polled during movement at same interval", Readable, multiple=True, optional=True), } def _getWaiters(self): return [] def doRead(self, maxage=0): for d in self._attached_polldevs: d.read(maxage) return Motor.doRead(self, maxage)