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 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), } 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 doStatus(self, maxage=0): sval = self._attached_bus.communicate('S?', self.addr, expect_hex=2) tval = self._attached_bus.communicate('T?', self.addr, expect_hex=2) return status.OK, 'status=%d, temperature=%d' % (sval, tval) @requires(level=ADMIN) def doStart(self, target): self._attached_bus.communicate('P%d' % (target == 'on'), self.addr, expect_ok=True)
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)
def doInit(self, mode): if self.mapping: self._reverse = {v: k for (k, v) in self.mapping.items()} # oneofdict: allows both types of values (string/int), but # normalizes them into the string form self.valuetype = oneofdict(self._reverse) self._forward = self.mapping return try: self._reverse, self._forward = \ parse_mapping(self._getProperty('mapping')) # we don't build the valuetype from self._reverse since it should # only contain the write-able values self.valuetype = oneofdict( {v: k for (k, v) in self._forward.items()}) except Exception: self.log.warning('could not parse value mapping from Tango', exc=1) self._reverse = self._forward = {}
class FRMChannel(BaseChannel, ActiveChannel): """Base class for one channel of the FRM II counter card. Use one of the concrete classes `FRMTimerChannel` or `FRMCounterChannel`. """ parameters = { 'mode': Param('Channel mode: normal, ratemeter, or preselection', type=oneofdict({ IOCommon.MODE_NORMAL: 'normal', IOCommon.MODE_RATEMETER: 'ratemeter', IOCommon.MODE_PRESELECTION: 'preselection' }), default='preselection', settable=True), } def doReadMode(self): modes = { IOCommon.MODE_NORMAL: 'normal', IOCommon.MODE_RATEMETER: 'ratemeter', IOCommon.MODE_PRESELECTION: 'preselection' } mode = self._taco_guard(self._dev.mode) if mode not in modes: self.log.warning('Unknown mode %r encountered!', mode) mode = IOCommon.MODE_NORMAL return modes[mode] def doWriteMode(self, value): modes = { 'normal': IOCommon.MODE_NORMAL, 'ratemeter': IOCommon.MODE_RATEMETER, 'preselection': IOCommon.MODE_PRESELECTION } self._taco_guard(self._dev.setMode, modes[value]) def doWriteIsmaster(self, value): if self.mode == 'ratemeter': if value: raise UsageError(self, 'ratemeter channel cannot be master') return self.doStop() self.mode = 'preselection' if value else 'normal' self._taco_guard(self._dev.enableMaster, value)
class AnaBlocks(AnalogOutput): parameters = { 'actionmode': Param('Block behavior', type=oneofdict(ACTIONMODES), default='default', settable=True, volatile=True), 'powertime': Param('How long to power pushing down blocks', type=int, settable=True, volatile=True), 'windowsize': Param('Window size', volatile=True, unit='deg'), 'blockwidth': Param('Block width', volatile=True, unit='deg'), 'blockoffset': Param('Block offset', volatile=True, unit='deg'), } def doReadActionmode(self): return ACTIONMODES[self._dev.GetParam('Param200')] def doWriteActionmode(self, value): mode = R_ACTIONMODES[value] self._dev.SetParam([[mode], ['Param200']]) def doReadPowertime(self): return self._dev.GetParam('Param204') def doWritePowertime(self, value): self._dev.SetParam([[value], ['Param204']]) def doReadWindowsize(self): return self._dev.GetParam('Param201') def doReadBlockwidth(self): return self._dev.GetParam('Param202') def doReadBlockoffset(self): return self._dev.GetParam('Param203')
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 IPCRelay(Moveable): """Makes the relay of an IPC single stepper card available as switch.""" parameter_overrides = { 'unit': Override(mandatory=False), } attached_devices = { 'stepper': Attach('The stepper card whose relay is controlled', Motor), } valuetype = oneofdict({0: 'off', 1: 'on'}) hardware_access = False def doStart(self, target): self._attached_stepper.relay = target def doRead(self, maxage=0): return self._attached_stepper.relay def doStatus(self, maxage=0): return status.OK, 'relay is %s' % self._attached_stepper.relay
class Sans1ColliMotorAllParams(Sans1ColliMotor): """ Device object for a digital output device via a Beckhoff modbus interface. Maximum Parameter Implementation. All Relevant Parameters are accessible and can be configured. """ _paridx = dict( refpos=2, vmax=3, v_max=3, vmin=4, v_min=4, vref=5, v_ref=5, acc=6, a_acc=6, ae=7, a_e=7, microsteps=8, backlash=9, fullsteps_u=10, fullsteps=10, imax=11, i_max=11, iv=12, i_v=12, iv0=12, i_v0=12, imin=12, i_min=12, encodersteps_u=20, features=30, t=40, temp=40, temperature=40, type=250, hw=251, fw=252, reset=255, ) parameters = { # provided by parent class: speed, unit, fmtstr, warnlimits, userlimits, # abslimits, precision and others 'power': Param( 'Power on/off for the motordriver and ' 'enable/disable for the logic', type=oneof('off', 'on'), default='off', settable=True), 'backlash': Param('Backlash correction in physical units', type=float, default=0.0, settable=True, mandatory=False, volatile=True), 'maxcurrent': Param('Max Motor current in A', type=floatrange(0.05, 5), settable=True, volatile=True), 'idlecurrent': Param('Idle Motor current in A', type=floatrange(0.05, 5), settable=True, volatile=True), 'temperature': Param('Temperature of the motor driver', type=float, settable=False, volatile=True), 'minspeed': Param('The minimum motor speed', unit='main/s', settable=True, volatile=True), 'refspeed': Param('The referencing speed', unit='main/s', settable=True, volatile=True), 'accel': Param('Acceleration/Decceleration', unit='main/s**2', settable=True, volatile=True), 'stopaccel': Param('Emergency Decceleration', unit='main/s**2', settable=True, volatile=True), # needed ? Ask the Sans1 people... 'hw_vmax': Param('Maximum Velocity in HW-units', type=intrange(1, 2047), settable=True, volatile=True), 'hw_vmin': Param('Minimum Velocity in HW-units', type=intrange(1, 2047), settable=True, volatile=True), 'hw_vref': Param('Referencing Velocity in HW-units', type=intrange(1, 2047), settable=True, volatile=True), 'hw_accel': Param('Acceleration in HW-units', type=intrange(16, 2047), settable=True, volatile=True), 'hw_accel_e': Param('Acceleration when hitting a limit switch in ' 'HW-units', type=intrange(16, 2047), settable=True, volatile=True), 'hw_backlash': Param( 'Backlash in HW-units', # don't use type=intrange(-32768, 32767), settable=True, volatile=True), 'hw_fullsteps': Param('Motor steps per turn in HW-units', type=intrange(1, 65535), settable=True, volatile=True), 'hw_enc_steps': Param('Encoder steps per turn in HW-units', type=intrange(1, 65535), settable=True, volatile=True), 'hw_features': Param('Value of features register (16 Bit, see docu)', type=intrange(0, 65535), settable=True, volatile=True), 'hw_type': Param('Value of features register (16 Bit, see docu)', type=int, settable=True, volatile=True), 'hw_revision': Param('Value of HW-revision register ' '(16 Bit, see docu)', type=int, settable=True, volatile=True), 'hw_firmware': Param('Value of HW-Firmware register ' '(16 Bit, see docu)', type=int, settable=True, volatile=True), 'hw_coderflt': Param('Input filter for Encoder signals', type=oneofdict({ 1: 'disabled', 0: 'enabled' }), default='enabled', settable=True, volatile=True), 'hw_feedback': Param('Feedback signal for positioning', type=oneofdict({ 0: 'encoder', 1: 'motor' }), default='motor', settable=True, volatile=True), 'hw_invposfb': Param('Turning direction of encoder', type=oneofdict({ 1: 'opposite', 0: 'concordant' }), default='concordant', settable=True, volatile=True), 'hw_ramptype': Param('Shape of accel/deccel ramp', type=oneofdict({ 1: 'exponential', 0: 'linear' }), default='linear', settable=True, volatile=True), 'hw_revin1': Param('Type of input 1', type=oneofdict({ 1: 'nc', 0: 'no' }), default='no', settable=True, volatile=True), 'hw_revin2': Param('Type of input 2', type=oneofdict({ 1: 'nc', 0: 'no' }), default='no', settable=True, volatile=True), 'hw_disin1rev': Param('Use Input 1 as reference input', type=oneofdict({ 1: 'off', 0: 'on' }), default='on', settable=True, volatile=True), 'hw_disin2rev': Param('Use Input 2 as reference input', type=oneofdict({ 1: 'off', 0: 'on' }), default='on', settable=True, volatile=True), 'hw_invrev': Param('Direction of reference drive', type=oneofdict({ 1: 'pos', 0: 'neg' }), default='neg', settable=True, volatile=True), } parameter_overrides = { 'microsteps': Override(mandatory=False, settable=True, volatile=True), 'refpos': Override(settable=True), } # more advanced stuff: setting/getting parameters # only to be used manually at the moment @usermethod @requires(level='user') def readParameter(self, index): self.log.debug('readParameter %d', index) try: index = int(self._paridx.get(index, index)) except ValueError: raise UsageError( self, 'Unknown parameter %r, try one of %s' % (index, ', '.join(self._paridx))) from None if self._readStatusWord() & (1 << 7): raise UsageError( self, 'Can not access Parameters while Motor is ' 'moving, please stop it first!') if self.power == 'on': self.power = 'off' # wait for inactive ACK/NACK self.log.debug('Wait for idle ACK/NACK bits') for _ in range(1000): if self._readStatusWord() & (3 << 14) == 0: break else: raise CommunicationError( self, 'HW still busy, can not read ' 'Parameter, please retry later.') self._writeUpperControlWord((index << 8) | 4) self.log.debug('Wait for ACK/NACK bits') for _ in range(1000): if self._readStatusWord() & (3 << 14) != 0: break else: raise CommunicationError( self, 'ReadPar command not recognized by ' 'HW, please retry later.') if self._readStatusWord() & (1 << 14): raise CommunicationError( self, 'Reading of Parameter %r failed, ' 'got a NACK' % index) return self._readReturn() @usermethod @requires(level='admin') def writeParameter(self, index, value, store2eeprom=False): self.log.debug('writeParameter %d:0x%04x', index, value) if store2eeprom: self.log.warning('writeParameter stores to eeprom !') try: index = int(self._paridx.get(index, index)) except ValueError: UsageError(self, 'Unknown parameter %r' % index) if self._readStatusWord() & (1 << 7): raise UsageError( self, 'Can not access Parameters while Motor is ' 'moving, please stop it first!') if self.power == 'on': self.power = 'off' # wait for inactive ACK/NACK self.log.debug('Wait for idle ACK/NACK bits') for _ in range(1000): if self._readStatusWord() & (3 << 14) == 0: break else: raise CommunicationError( self, 'HW still busy, can not write ' 'Parameter, please retry later.') self._writeDestination(value) if store2eeprom: # store to eeprom self._writeUpperControlWord((index << 8) | 3) else: # store to volatile memory self._writeUpperControlWord((index << 8) | 1) self.log.debug('Wait for ACK/NACK bits') for _ in range(1000): if self._readStatusWord() & (3 << 14) != 0: break else: raise CommunicationError( self, 'WritePar command not recognized ' 'by HW, please retry later.') if self._readStatusWord() & (1 << 14): raise CommunicationError( self, 'Writing of Parameter %r failed, ' 'got a NACK' % index) return self._readReturn() # # Parameter access methods # def doWritePower(self, value): if self._readStatusWord() & (1 << 7): raise UsageError( self, 'Never switch off Power while Motor is ' 'moving !') value = ['off', 'on'].index(value) # docu: bit0 = enable/disable self._writeControlBit(0, value) def doReadPower(self): # docu: bit0 = enable/disable return ['off', 'on'][self._readControlBit(0)] # Parameter 1 : CurrentPosition def doSetPosition(self, value): self.writeParameter(1, self._phys2steps(value)) # Parameter 2 : Refpos def doReadRefpos(self): return self._steps2phys(self.readParameter(2)) def doWriteRefpos(self, value): self.writeParameter(2, self._phys2steps(value), store2eeprom=True) # Parameter 3 : hw_vmax -> speed def doReadHw_Vmax(self): return self.readParameter(3) def doReadSpeed(self): return self._speed2phys(self.hw_vmax) # units per second def doWriteHw_Vmax(self, value): self.writeParameter(3, value) def doWriteSpeed(self, speed): self.hw_vmax = self._phys2speed(speed) # Parameter 4 : hw_vmin -> minspeed def doReadHw_Vmin(self): return self.readParameter(4) def doReadMinspeed(self): return self._speed2phys(self.hw_vmin) # units per second def doWriteHw_Vmin(self, value): self.writeParameter(4, value) def doWriteMinspeed(self, speed): self.hw_vmin = self._phys2speed(speed) # Parameter 5 : hw_vref -> refspeed def doReadHw_Vref(self): return self.readParameter(5) # µSteps per second def doReadRefspeed(self): return self._speed2phys(self.hw_vref) # units per second def doWriteHw_Vref(self, value): self.writeParameter(5, value) def doWriteRefspeed(self, speed): self.hw_vref = self._phys2speed(speed) # Parameter 6 : hw_accel -> accel def doReadHw_Accel(self): return self.readParameter(6) # µSteps per second def doReadAccel(self): return self._speed2phys(self.hw_accel) # units per second def doWriteHw_Accel(self, value): self.writeParameter(6, value) def doWriteAccel(self, accel): self.hw_accel = self._phys2speed(accel) # Parameter 7 : hw_accel_e -> stopaccel def doReadHw_Accel_E(self): return self.readParameter(7) # µSteps per second def doReadStopaccel(self): return self._speed2phys(self.hw_accel_e) # units per second def doWriteHw_Accel_E(self, value): self.writeParameter(7, value) def doWriteStopaccel(self, accel): self.hw_accel_e = self._phys2speed(accel) # Parameter 8 : microsteps def doWriteMicrosteps(self, value): for i in range(7): if value == 2**i: self.writeParameter(8, i) break else: raise InvalidValueError( self, 'This should never happen! value should be one of: ' '1, 2, 4, 8, 16, 32, 64 !') def doReadMicrosteps(self): return 2**self.readParameter(8) # Parameter 9 : hw_backlash -> backlash def doReadHw_Backlash(self): return self.readParameter(9) # µSteps per second def doReadBacklash(self): return self._steps2phys(self.hw_backlash) def doWriteHw_Backlash(self, value): self.writeParameter(9, value) def doWriteBacklash(self, value): self.hw_backlash = self._phys2steps(value) # Parameter 10 : Fullsteps per turn def doReadHw_Fullsteps(self): return self.readParameter(10) def doWriteHw_Fullsteps(self, value): self.writeParameter(10, value) # Parameter 11 : MaxCurrent def doReadMaxcurrent(self): return self.readParameter(11) * 0.05 def doWriteMaxcurrent(self, value): self.writeParameter(11, int(0.5 + value / 0.05)) # Parameter 12 : IdleCurrent def doReadIdlecurrent(self): return self.readParameter(12) * 0.05 def doWriteIdlecurrent(self, value): self.writeParameter(12, int(0.5 + value / 0.05)) # Parameter 20 : Encodersteps per turn def doReadHw_Enc_Steps(self): return self.readParameter(20) def doWriteHw_Enc_Steps(self, value): self.writeParameter(20, value) # Parameter 30 : Features def doReadHw_Features(self): value = self.readParameter(30) self.log.debug('Feature0: Inputfilter for encodersignals: %d', value & 1) self.log.debug( 'Feature1: Positionsrueckfuehrung (0=Encoder, ' '1=Zaehler): %d', (value >> 1) & 1) self.log.debug( 'Feature2: Zaehlrichtung encoder (0=mitlaufend, ' '1=gegenlaufend): %d', (value >> 2) & 1) self.log.debug('Feature3: Bremsrampe (0=linear, 1=exponentiell): %d', (value >> 3) & 1) self.log.debug('Feature4: Eingang1 (0=Schliesser, 1=oeffner): %d', (value >> 4) & 1) self.log.debug('Feature5: Eingang2 (0=Schliesser, 1=oeffner): %d', (value >> 5) & 1) self.log.debug('Feature6: Eingang1 (0=referenz, 1=normal): %d', (value >> 6) & 1) self.log.debug('Feature7: Eingang2 (0=referenz, 1=normal): %d', (value >> 7) & 1) self.log.debug( 'Feature8: Richtung der Referenzfahrt (0=negativ, ' '1=positiv): %d', (value >> 8) & 1) return value def doWriteHw_Features(self, value): self.writeParameter(30, value) # bitwise access def doReadHw_Coderflt(self): return (self.hw_features >> 0) & 1 def doWriteHw_Coderflt(self, value): if value in [0, 1]: self.hw_features = (self.hw_features & ~(1 << 0)) | (value << 0) else: raise InvalidValueError(self, 'hw_disencfltr can only be 0 or 1') def doReadHw_Feedback(self): return (self.hw_features >> 1) & 1 def doWriteHw_Feedback(self, value): if value in [0, 1]: self.hw_features = (self.hw_features & ~(1 << 1)) | (value << 1) else: raise InvalidValueError(self, 'hw_feedback can only be 0 or 1') def doReadHw_Invposfb(self): return (self.hw_features >> 2) & 1 def doWriteHw_Invposfb(self, value): if value in [0, 1]: self.hw_features = (self.hw_features & ~(1 << 2)) | (value << 2) else: raise InvalidValueError(self, 'hw_invposfb can only be 0 or 1') def doReadHw_Ramptype(self): return (self.hw_features >> 3) & 1 def doWriteHw_Ramptype(self, value): if value in [0, 1]: self.hw_features = (self.hw_features & ~(1 << 3)) | (value << 3) else: raise InvalidValueError(self, 'hw_ramptype can only be 0 or 1') def doReadHw_Revin1(self): return (self.hw_features >> 4) & 1 def doWriteHw_Revin1(self, value): if value in [0, 1]: self.hw_features = (self.hw_features & ~(1 << 4)) | (value << 4) else: raise InvalidValueError(self, 'hw_revin1 can only be 0 or 1') def doReadHw_Revin2(self): return (self.hw_features >> 5) & 1 def doWriteHw_Revin2(self, value): if value in [0, 1]: self.hw_features = (self.hw_features & ~(1 << 5)) | (value << 5) else: raise InvalidValueError(self, 'hw_revin2 can only be 0 or 1') def doReadHw_Disin1Rev(self): return (self.hw_features >> 6) & 1 def doWriteHw_Disin1Rev(self, value): if value in [0, 1]: self.hw_features = (self.hw_features & ~(1 << 6)) | (value << 6) else: raise InvalidValueError(self, 'hw_disin1rev can only be 0 or 1') def doReadHw_Disin2Rev(self): return (self.hw_features >> 7) & 1 def doWriteHw_Disin2Rev(self, value): if value in [0, 1]: self.hw_features = (self.hw_features & ~(1 << 7)) | (value << 7) else: raise InvalidValueError(self, 'hw_disin2rev can only be 0 or 1') def doReadHw_Invrev(self): return (self.hw_features >> 8) & 1 def doWriteHw_Invrev(self, value): if value in [0, 1]: self.hw_features = (self.hw_features & ~(1 << 8)) | (value << 8) else: raise InvalidValueError(self, 'hw_invrev can only be 0 or 1') # Parameter 40 : Temperature def doReadTemperature(self): return self.readParameter(40) # Parameter 250 : Klemmentyp def doReadHw_Type(self): return self.readParameter(250) # Parameter 251 : Hardwarestand def doReadHw_Revision(self): return self.readParameter(251) # Parameter 252 : Firmwarestand def doReadHw_Firmware(self): return self.readParameter(252) # Parameter 255 : Factory Reset @usermethod def FactoryReset(self, password): """resets the motorcontroller to factory default values for the right password see docu""" # 0x544B4531 self.writeParameter(255, password)
class Sans1ColliMotor(Sans1ColliBase, CanReference, SequencerMixin, HasTimeout, Motor): """ Device object for a digital output device via a Beckhoff modbus interface. Minimum Parameter Implementation. Relevant Parameters need to be configured in the setupfile or in the Beckhoff PLC. """ relax_mapping = True parameters = { # provided by parent class: speed, unit, fmtstr, warnlimits, abslimits, # userlimits, precision and others 'address': Param('Starting offset of Motor control Block in words', type=MOTOR_VALIDATOR, mandatory=True, settable=False, userparam=False), 'slope': Param('Slope of the Motor in FULL steps per physical ' 'unit', type=float, default=1., unit='steps/main', userparam=False, settable=True), 'microsteps': Param('Microstepping for the motor', type=oneof(1, 2, 4, 8, 16, 32, 64), default=1, userparam=False, settable=False), 'autozero': Param( 'Maximum distance from referencepoint for forced ' 'referencing before moving, or None', type=none_or(float), default=10, unit='main', settable=False), 'autopower': Param('Automatically disable Drivers if motor is idle', type=oneofdict({ 0: 'off', 1: 'on' }), default='on', settable=False), 'refpos': Param('Position of reference switch', unit='main', type=float, mandatory=True, settable=False, prefercache=False), } parameter_overrides = { 'timeout': Override(mandatory=False, default=300), } _busy_until = 0 def doInit(self, mode): Sans1ColliBase.doInit(self, mode) if mode != SIMULATION: if self.autopower == 'on': self._HW_disable() # # access-helpers for accessing the fields inside the MotorControlBlock # def _readControlBit(self, bit): self.log.debug('_readControlBit %d', bit) value = uint16(self._dev.ReadOutputWord(self.address)) return (value & (1 << int(bit))) >> int(bit) def _writeControlBit(self, bit, value): self._busy_until = time.time() + 3 self.log.debug('_writeControlBit %r, %r', bit, value) tmpval = uint16(self._dev.ReadOutputWord(self.address)) tmpval &= ~(1 << int(bit)) tmpval |= (int(value) << int(bit)) self._dev.WriteOutputWord((self.address, uint16(tmpval))) session.delay(0.5) # work around race conditions inside plc.... def _writeDestination(self, value): self.log.debug('_writeDestination %r', value) self._dev.WriteOutputDword((self.address + 2, uint32(value))) def _readStatusWord(self): value = uint16(self._dev.ReadOutputWord(self.address + 4)) self.log.debug('_readStatusWord %04x', value) return value def _readErrorWord(self): value = uint16(self._dev.ReadOutputWord(self.address + 5)) self.log.debug('_readErrorWord %04x', value) return value def _readPosition(self): value = int32(self._dev.ReadOutputDword(self.address + 6)) self.log.debug('_readPosition: -> %d steps', value) return value def _readUpperControlWord(self): self.log.error('_readUpperControlWord') return uint16(self._dev.ReadOutputWord(self.address + 1)) def _writeUpperControlWord(self, value): self.log.debug('_writeUpperControlWord 0x%04x', value) value = uint16(value) self._dev.WriteOutputWord((self.address + 1, value)) def _readDestination(self): value = int32(self._dev.ReadOutputDword(self.address + 2)) self.log.debug('_readDestination: -> %d steps', value) return value def _readReturn(self): value = int32(self._dev.ReadOutputDword(self.address + 8)) self.log.debug('_readReturn: -> %d (0x%08x)', value, value) return value # # math: transformation of position and speed: # µsteps(/s) <-> phys. unit(/s) # def _steps2phys(self, steps): value = steps / float(self.microsteps * self.slope) self.log.debug('_steps2phys: %r steps -> %s', steps, self.format(value, unit=True)) return value def _phys2steps(self, value): steps = int(value * float(self.microsteps * self.slope)) self.log.debug('_phys2steps: %s -> %r steps', self.format(value, unit=True), steps) return steps def _speed2phys(self, speed): # see manual return speed / float(self.microsteps * self.slope * 1.6384e-2) def _phys2speed(self, value): # see manual return int(value * self.slope * self.microsteps * 1.6384e-2) # # Hardware abstraction: which actions do we want to do... # def _HW_enable(self): self._writeControlBit(0, 1) # docu: bit0 = 1: enable def _HW_disable(self): self._writeControlBit(0, 0) # docu: bit0 = 1: enable def _HW_start(self, target): self._writeDestination(self._phys2steps(target)) # docu: bit2 = go to absolute position, autoresets self._writeControlBit(2, 1) def _HW_reference(self): """Do the referencing and update position to refpos""" self._writeControlBit(4, 1) # docu: bit4 = reference, autoresets # according to docu, the refpos is (also) a parameter of the KL.... def doSetPosition(self, value): for _ in range(100): if self._readStatusWord() & (1 << 7): continue break else: raise UsageError( self, 'Can not set position while motor is ' 'moving, please stop it first!') was_on = self._readControlBit(0) if was_on: self._HW_disable() # wait for inactive ACK/NACK for _ in range(1000): if self._readStatusWord() & (3 << 14) == 0: break else: raise CommunicationError( self, 'HW still busy, can not set ' 'position, please retry later.') loops = 10 for loop in range(loops): self.log.debug('setPosition: loop %d of %d', loop, loops) self._writeDestination(self._phys2steps(value)) # index=1: update current position self._writeUpperControlWord((1 << 8) | 1) # Wait for ACK/NACK bits for _ in range(100): last_sw = self._readStatusWord() if last_sw & (3 << 14) != 0: break else: self.log.warning( 'SetPosition command not recognized, retrying') if last_sw & (2 << 14) != 0: self.log.debug('setPosition: got ACK') break elif last_sw & (1 << 14): self.log.debug('setPosition: got NACK') raise CommunicationError( self, 'Setting position failed, ' 'got a NACK!') else: raise CommunicationError( self, 'setPosition command not ' 'recognized by HW, please retry later.') if was_on: self._HW_enable() def _HW_stop(self): self._writeControlBit(6, 1) # docu: bit6 = stop, autoresets def _HW_wait_while_BUSY(self): # XXX timeout? while not self._seq_stopflag: session.delay(0.1) statval = self._readStatusWord() # if motor moving==0 and target reached==1 -> break if (statval & (1 << 7) == 0) and (statval & (1 << 6)): break if statval & (7 << 10): # limit switch triggered or stop issued session.delay(0.1) break def _HW_status(self): """ used Status bits: 0-2 : Load-angle (0 good, 7 bad) 3 : limit switch - 4 : limit switch + 5 : moving in pos. direction 6 : target reached 7 : motor moving 8 : driver on and ready 9 : Overtemperature 10 : Target NOT reached, but a limit switch triggered 11 : Target NOT reached due PowerOff or Stop 12 : Can not move towards requested position, command ignored 14 : N_ACK (last set/get command was unsuccessful), auto clears after 1s 15 : ACK (last get/set command was successful, value in RETURN is valid), auto clears after 1s """ statval = self._readStatusWord() errval = self._readErrorWord() code, msg = status.ERROR, ['Unknown Status value 0x%04x!' % statval] # work around buggy SPS code: # sometimes we get 0x0105..7, which should never happen # as the lowest 3 bits are not relevant, # check only the others and return BUSY # also ignore the limit switch bits # if statval & (0xfff8) == 0x0100: if statval & (0x7fe0) == 0x0100: return status.BUSY, '0x010x!' # status Stuff if statval & (1 << 7): code, msg = status.BUSY, ['busy'] elif statval & (1 << 6): code, msg = status.OK, ['Target reached'] elif ~statval & (1 << 8): code, msg = status.OK, ['Disabled'] elif statval & (1 << 9): code, msg = status.ERROR, ['Overtemperature!'] # check any of bit 10, 11, 12 at the same time! elif statval & (7 << 10): code, msg = status.OK, ['Can not reach Target!'] if errval: code, msg = status.ERROR, ['Error'] if errval & (1 << 0): msg.append('Control voltage too low') if errval & (1 << 1): msg.append('Motor driving voltage too low') if errval & (1 << 2): msg.append('Overcurrent or short in winding A') if errval & (1 << 3): msg.append('Overcurrent or short in winding B') if errval & (1 << 4): msg.append('Open load or broken wire in winding A') if errval & (1 << 5): msg.append('Open load or broken wire in winding B') if errval & (1 << 7): msg.append('Overtemperature (T>125 degC)') if errval & 0b1111111101000000: msg.append('Unknown Error 0x%04x' % errval) # informational stuff if statval & (1 << 4): msg.append('limit switch +') if statval & (1 << 3): msg.append('limit switch -') if statval & (1 << 8): msg.append('driver on and ready') if statval & (1 << 7): msg.append('load=%d' % (statval & 0x0007)) msg = ', '.join(msg) self.log.debug('_HW_Status returns %r', (code, msg)) if self._busy_until > time.time(): code = max(code, status.BUSY) msg = 'timed busy, %s' % msg return code, msg # # Sequencing stuff # def _gen_move_sequence(self, target): # now generate a sequence of commands to execute in order seq = [] # always enable before doing anything seq.append(SeqMethod(self, '_HW_enable')) # check autoreferencing feature if self.autozero is not None: currentpos = self.read(0) mindist = min(abs(currentpos - self.refpos), abs(target - self.refpos)) if mindist < self.autozero: seq.extend(self._gen_ref_sequence()) # now just go where commanded.... seq.append(SeqMethod(self, '_HW_start', target)) seq.append(SeqMethod(self, '_HW_wait_while_BUSY')) if self.autopower == 'on': # disable if requested. seq.append(SeqMethod(self, '_HW_disable')) return seq def _gen_ref_sequence(self): seq = [] # try to mimic anatel: go to 5mm before refpos and then to the negative limit switch seq.append(SeqMethod(self, '_HW_enable')) seq.append(SeqMethod(self, '_HW_start', self.refpos + 5.)) seq.append(SeqMethod(self, '_HW_wait_while_BUSY')) seq.append( SeqMethod( self, '_HW_start', self.absmin if self.absmin < self.refpos else self.refpos - 100)) seq.append(SeqMethod(self, '_HW_wait_while_BUSY')) seq.append(SeqMethod(self, '_HW_reference')) seq.append(SeqMethod(self, '_HW_wait_while_BUSY')) seq.append(SeqMethod(self, 'doSetPosition', self.refpos)) return seq # # nicos methods # def doRead(self, maxage=0): return self._steps2phys(self._readPosition()) def doStart(self, target): if self._seq_is_running(): raise MoveError(self, 'Cannot start device, it is still moving!') self._startSequence(self._gen_move_sequence(target)) def doStop(self): if self._honor_stop: self._seq_stopflag = True self._HW_stop() def doReset(self): self._writeControlBit(7, 1) # docu: bit7 = ERROR-ACK, autoresets self._set_seq_status(status.OK, 'idle') def doStatus(self, maxage=0): """returns highest statusvalue""" if self._mode == SIMULATION: stati = [(status.OK, 'simulation'), self._seq_status] else: stati = [self._HW_status(), self._seq_status] # sort inplace by first element, i.e. status code stati.sort(key=lambda st: st[0]) # select highest (worst) status # if no status is 'worse' then _seq_status, this is _seq_status _status = stati[-1] if self._seq_is_running(): return max(status.BUSY, _status[0]), _status[1] return _status @requires(level='admin') def doReference(self): if self._seq_is_running(): raise MoveError(self, 'Cannot reference a moving device!') seq = self._gen_ref_sequence() if self.autopower == 'on': # disable if requested. seq.append(SeqMethod(self, '_HW_disable')) self._startSequence(seq)
class Motor(HasTimeout, NicosMotor): """This class supports IPC 6-fold, 3-fold and single motor cards. It can be used with the `nicos.devices.generic.Axis` class. """ parameters = { 'addr': Param( 'Bus address of the motor', # 0 for the profibus card adaptor type=oneof(*([0] + list(range(32, 256)))), mandatory=True), 'unit': Param('Motor unit', type=str, default='steps'), 'zerosteps': Param('Motor steps for physical zero', type=float, unit='steps', settable=True), 'slope': Param('Motor slope', type=float, settable=True, unit='steps/main', default=1.0), # those parameters come from the card 'firmware': Param('Firmware version', type=int), 'steps': Param('Last position in steps', settable=True, type=intrange(0, 999999), prefercache=False), 'speed': Param('Motor speed (0..255)', settable=True, type=intrange(0, 255)), 'accel': Param('Motor acceleration (0..255)', settable=True, type=intrange(0, 255)), 'confbyte': Param('Configuration byte', type=intrange(-1, 255), settable=True), 'ramptype': Param('Ramp type', settable=True, type=intrange(1, 4)), 'microstep': Param('Microstepping mode', unit='steps', settable=True, type=oneof(1, 2, 4, 8, 16)), 'min': Param('Lower motorlimit', settable=True, type=intrange(0, 999999), unit='steps'), 'max': Param('Upper motorlimit', settable=True, type=intrange(0, 999999), unit='steps'), 'startdelay': Param('Start delay', type=floatrange(0, 25), unit='s', settable=True, volatile=True), 'stopdelay': Param('Stop delay', type=floatrange(0, 25), unit='s', settable=True, volatile=True), 'divider': Param('Speed divider', settable=True, type=intrange(-1, 7)), # volatile parameters to read/switch card features 'inhibit': Param('Inhibit input', default='off', volatile=True, type=oneofdict({ 0: 'off', 1: 'on' })), 'relay': Param('Relay switch', type=oneofdict({ 0: 'off', 1: 'on' }), settable=True, default='off', volatile=True), 'power': Param('Internal power stage switch', default='on', type=oneofdict({ 0: 'off', 1: 'on' }), settable=True, volatile=True), } parameter_overrides = { 'timeout': Override(mandatory=False, default=360), } attached_devices = { 'bus': Attach('The communication bus', IPCModBus), } errorstates = () def doInit(self, mode): if mode != SIMULATION: self._attached_bus.ping(self.addr) if self._hwtype == 'single': if self.confbyte != self.doReadConfbyte(): self.doWriteConfbyte(self.confbyte) self.log.warning( 'Confbyte mismatch between setup and ' 'card, overriding card value to 0x%02x', self.confbyte) # make sure that the card has the right "last steps" if self.steps != self.doReadSteps(): self.doWriteSteps(self.steps) self.log.warning( 'Resetting stepper position to last known ' 'good value %d', self.steps) self._type = 'stepper motor, ' + self._hwtype else: self._type = 'simulated stepper' def doVersion(self): try: version = self._attached_bus.get(self.addr, 137) except InvalidCommandError: return [] else: return [('IPC motor card', str(version))] def _tosteps(self, value): return int(float(value) * self.slope + self.zerosteps + 0.5) def _fromsteps(self, value): return float(value - self.zerosteps) / self.slope @lazy_property def _hwtype(self): """Returns 'single', 'triple', or 'sixfold', used for features that only one of the card types supports. """ if self._mode == SIMULATION: return 'single' # can't determine value in simulation mode! if self.doReadDivider() == -1: try: self._attached_bus.get(self.addr, 142) return 'sixfold' except InvalidCommandError: return 'single' return 'triple' def doReadUserlimits(self): if self.slope < 0: return (self._fromsteps(self.max), self._fromsteps(self.min)) else: return (self._fromsteps(self.min), self._fromsteps(self.max)) def doWriteUserlimits(self, limits): NicosMotor.doWriteUserlimits(self, limits) if self.slope < 0: self.min = self._tosteps(limits[1]) self.max = self._tosteps(limits[0]) else: self.min = self._tosteps(limits[0]) self.max = self._tosteps(limits[1]) def doReadSpeed(self): return self._attached_bus.get(self.addr, 128) def doWriteSpeed(self, value): self._attached_bus.send(self.addr, 41, value, 3) def doReadAccel(self): return self._attached_bus.get(self.addr, 129) def doWriteAccel(self, value): if self._hwtype != 'single' and value > 31: raise ValueError( self, 'acceleration value %d too big for ' 'non-single cards' % value) self._attached_bus.send(self.addr, 42, value, 3) self.log.info('parameter change not permanent, use _store() ' 'method to write to EEPROM') def doReadRamptype(self): try: return self._attached_bus.get(self.addr, 136) except InvalidCommandError: return 1 def doWriteRamptype(self, value): try: self._attached_bus.send(self.addr, 50, value, 3) except InvalidCommandError as err: raise InvalidValueError(self, 'ramp type not supported by card') from err self.log.info('parameter change not permanent, use _store() ' 'method to write to EEPROM') def doReadDivider(self): if self._mode == SIMULATION: return -1 # can't determine value in simulation mode! try: return self._attached_bus.get(self.addr, 144) except InvalidCommandError: return -1 def doWriteDivider(self, value): try: self._attached_bus.send(self.addr, 60, value, 3) except InvalidCommandError as err: raise InvalidValueError(self, 'divider not supported by card') from err self.log.info('parameter change not permanent, use _store() ' 'method to write to EEPROM') def doReadMicrostep(self): try: # microstepping cards return [1, 2, 4, 8, 16][self._attached_bus.get(self.addr, 141)] except InvalidCommandError: # simple cards only support full or half steps return [1, 2][(self._attached_bus.get(self.addr, 134) & 4) >> 2] def doWriteMicrostep(self, value): if self._hwtype == 'single': if value == 1: self._attached_bus.send(self.addr, 36) elif value == 2: self._attached_bus.send(self.addr, 37) else: raise InvalidValueError( self, 'microsteps > 2 not supported by card') else: self._attached_bus.send(self.addr, 57, [1, 2, 4, 8, 16].index(value), 3) self.log.info('parameter change not permanent, use _store() ' 'method to write to EEPROM') def doReadMax(self): return self._attached_bus.get(self.addr, 131) def doWriteMax(self, value): self._attached_bus.send(self.addr, 44, value, 6) def doReadMin(self): return self._attached_bus.get(self.addr, 132) def doWriteMin(self, value): self._attached_bus.send(self.addr, 45, value, 6) def doReadStepsize(self): return bool(self._attached_bus.get(self.addr, 134) & 4) def doReadConfbyte(self): try: return self._attached_bus.get(self.addr, 135) except InvalidCommandError: return -1 def doWriteConfbyte(self, value): if self._hwtype == 'single': self._attached_bus.send(self.addr, 49, value, 3) else: raise InvalidValueError(self, 'confbyte not supported by card') self.log.info('parameter change not permanent, use _store() ' 'method to write to EEPROM') def doReadStartdelay(self): if self.firmware > 40: try: return self._attached_bus.get(self.addr, 139) / 10.0 except InvalidCommandError: return 0.0 else: return 0.0 def doWriteStartdelay(self, value): if self._hwtype == 'single': self._attached_bus.send(self.addr, 55, int(value * 10), 3) else: raise InvalidValueError(self, 'startdelay not supported by card') self.log.info('parameter change not permanent, use _store() ' 'method to write to EEPROM') def doReadStopdelay(self): if self.firmware > 44: try: return self._attached_bus.get(self.addr, 143) / 10.0 except InvalidCommandError: return 0.0 else: return 0.0 def doWriteStopdelay(self, value): if self._hwtype == 'single': self._attached_bus.send(self.addr, 58, int(value * 10), 3) else: raise InvalidValueError(self, 'stopdelay not supported by card') self.log.info('parameter change not permanent, use _store() ' 'method to write to EEPROM') def doReadFirmware(self): return self._attached_bus.get(self.addr, 137) def doReadSteps(self): return self._attached_bus.get(self.addr, 130) def doWriteSteps(self, value): self.log.debug('setting new steps value: %s', value) self._attached_bus.send(self.addr, 43, value, 6) def doWritePrecision(self, precision): minprec = abs(2. / self.slope) if precision < minprec: self.log.warning('Precision needs to be at least %.3f, adjusting.', minprec) return minprec def doStart(self, target): bus = self._attached_bus target = self._tosteps(target) self.log.debug('target is %d steps', target) self._hw_wait() pos = self._tosteps(self.read(0)) self.log.debug('pos is %d steps', pos) diff = target - pos if diff == 0: return elif diff < 0: bus.send(self.addr, 35) else: bus.send(self.addr, 34) bus.send(self.addr, 46, abs(diff), 6) session.delay(0.1) # moved here from doWait. def doReset(self): bus = self._attached_bus if self.status(0)[0] != status.OK: # busy or error bus.send(self.addr, 33) # stop try: # this might take a while, ignore errors self._hw_wait() except Exception: pass # remember current state actpos = bus.get(self.addr, 130) speed = bus.get(self.addr, 128) accel = bus.get(self.addr, 129) minstep = bus.get(self.addr, 132) maxstep = bus.get(self.addr, 131) bus.send(self.addr, 31) # reset card session.delay(0.2) if self._hwtype != 'single': # triple and sixfold cards need a LONG time for resetting session.delay(2) # update state bus.send(self.addr, 41, speed, 3) bus.send(self.addr, 42, accel, 3) bus.send(self.addr, 45, minstep, 6) bus.send(self.addr, 44, maxstep, 6) bus.send(self.addr, 43, actpos, 6) def doStop(self): if self._hwtype == 'single': self._attached_bus.send(self.addr, 52) else: self._attached_bus.send(self.addr, 33) session.delay(0.2) def doRead(self, maxage=0): value = self._attached_bus.get(self.addr, 130) self._params['steps'] = value # save last valid position in cache if self._cache: self._cache.put(self, 'steps', value) self.log.debug('value is %d', value) return self._fromsteps(value) def doReadRelay(self): return 'on' if self._attached_bus.get(self.addr, 134) & 8 else 'off' def doWriteRelay(self, value): if value in [0, 'off']: self._attached_bus.send(self.addr, 39) elif value in [1, 'on']: self._attached_bus.send(self.addr, 38) def doReadInhibit(self): return (self._attached_bus.get(self.addr, 134) & 16) == 16 def doReadPower(self): return 'on' if self._attached_bus.get(self.addr, 134) & 16384 else 'off' def doWritePower(self, value): if value in [0, 'off']: self._attached_bus.send(self.addr, 53) elif value in [1, 'on']: self._attached_bus.send(self.addr, 54) def doReadPrecision(self): # precision: 1 step return 2. / abs(self.slope) def doStatus(self, maxage=0): state = self._attached_bus.get(self.addr, 134) st = status.OK msg = '' # msg += (state & 0x2) and ', backward' or ', forward' # msg += (state & 0x4) and ', halfsteps' or ', fullsteps' if state & 0x10: msg += ', inhibit active' if state & 0x80: msg += ', reference switch active' if state & 0x100: msg += ', software limit - reached' if state & 0x200: msg += ', software limit + reached' if state & 0x4000 == 0: msg += ', external power stage enabled' if state & 0x20: if self._hwtype == 'sixfold' and self.firmware < 63: msg += ', limit switch + active' else: msg += ', limit switch - active' if state & 0x40: if self._hwtype == 'sixfold' and self.firmware < 63: msg += ', limit switch - active' else: msg += ', limit switch + active' if self._hwtype == 'single': msg += (state & 0x8) and ', relais on' or ', relais off' if state & 0x8: # on single cards, if relay is ON, card is supposedly BUSY st = status.BUSY if state & 0x8000: st = status.BUSY msg += ', waiting for start/stopdelay' # check error states last if state & (0x20 | 0x40) == 0x60: st = status.ERROR msg = msg.replace( 'limit switch - active, limit switch + active', 'EMERGENCY STOP pressed or both limit switches ' 'broken') if state & 0x400: st = status.ERROR msg += ', device overheated' if state & 0x800: st = status.ERROR msg += ', motor undervoltage' if state & 0x1000: st = status.ERROR msg += ', motor not connected or leads broken' if state & 0x2000: st = status.ERROR msg += ', hardware failure or device not reset after power-on' # if it's moving, it's not in error state! (except if the emergency # stop is active) if state & 0x1 and (state & (0x20 | 0x40) != 0x60): st = status.BUSY msg = ', moving' + msg self.log.debug('status is %d:%s', st, msg[2:]) return st, msg[2:] def doSetPosition(self, target): """Adjust the current stepper position of the IPC-stepper card to match the given position. This is in contrast to the normal behaviour which just adjusts the zerosteps, but IPC cards have a limited range, so it is crucial to stay within that. So we 'set' the position of the card instead of adjusting our zerosteps. """ self.log.debug('setPosition: %s', target) value = self._tosteps(target) self.doWriteSteps(value) self._params['steps'] = value # save last valid position in cache if self._cache: self._cache.put(self, 'steps', value) @usermethod def _store(self): self._attached_bus.send(self.addr, 40) self.log.info('parameters stored to EEPROM') @usermethod def _printconfig(self): byte = self.confbyte c = '' if byte & 1: c += 'limit switch 1: high = active\n' else: c += 'limit switch 1: low = active\n' if byte & 2: c += 'limit switch 2: high = active\n' else: c += 'limit switch 2: low = active\n' if byte & 4: c += 'inhibit entry: high = active\n' else: c += 'inhibit entry: low = active\n' if byte & 8: c += 'reference switch: high = active\n' else: c += 'reference switch: low = active\n' if byte & 16: c += 'use external powerstage\n' else: c += 'use internal powerstage\n' if byte & 32: c += 'leads testing disabled\n' else: c += 'leads testing enabled\n' if byte & 64: c += 'reversed limit switches\n' else: c += 'normal limit switch order\n' if byte & 128: c += 'freq-range: 8-300Hz\n' else: c += 'freq-range: 90-3000Hz\n' self.log.info(c)
def doInit(self, mode): self._reverse = {v: k for (k, v) in self.mapping.items()} # oneofdict: allows both types of values (string/int), but normalizes # them into the string form self.valuetype = oneofdict(self._reverse)
class MCC2Motor(MCC2core, NicosMotor): """Class for the control of the MCC2-Stepper""" movementTypes = ('rotational', 'linear') parameters = { 'mccmovement': Param('Type of movement, change behaviour of limit ' 'switches', type=oneof(*movementTypes), default='linear', settable=True, prefercache=False), 'slope': Param('Full motor steps per physical unit', type=float, default=1, unit='1/main', prefercache=False), 'power': Param('Internal power stage switch', default='on', type=oneofdict({ 0: 'off', 1: 'on' }), settable=True, volatile=True), 'steps': Param('Last position in steps', settable=True, type=int, prefercache=False), 'accel': Param('Motor acceleration in physical units', prefercache=False, type=float, settable=True, unit='1/main**2'), 'microstep': Param('Microstepping mode', unit='microsteps/fullstep', type=intrange(1, 255), settable=True, prefercache=False), 'idlecurrent': Param('Current whenever motor is Idle', unit='A', type=floatrange(0, 2.5), settable=True, prefercache=False), 'rampcurrent': Param('Current whenever motor is Ramping', unit='A', type=floatrange(0, 2.5), settable=True, prefercache=False), 'movecurrent': Param('Current whenever motor is moving at speed', type=floatrange(0, 2.5), unit='A', prefercache=False, settable=True), 'linear': Param('Linear stage (as opposed to choppered stage)', type=bool, settable=False, volatile=True), } def doReset(self): self.comm('XC') # Reset Axis (handbook is vague...) self.comm('XP02S1') # unit = steps self.comm('XP03S1') # unity slope self.comm('XP04S20') # lowest frequency which is Ok whithout ramp self.comm('XP17S2') # ramping uses boostcurrent # no backlash correction, this is done in the axis code self.comm('XP25S0') # Limit switches are openers (normally closed=n.c.) self.comm('XP27S0') @usermethod def printcurrents(self): """Print the settings of the currents. The MCC-2 motor controller has different settings for the: - idle current - moving current - ramp current These settings will be displayed. """ if self.linear: const = 0.05 # SHOULD BE 0.04! else: const = 0.1 xi = const * float(self.comm('XP40R')) xm = const * float(self.comm('XP41R')) xr = const * float(self.comm('XP42R')) self.log.info('MCC-2 currents of this axes are:') self.log.info('idle: %f', xi) self.log.info('move: %f', xm) self.log.info('ramp: %f', xr) def doReadMccmovement(self): return self.movementTypes[int(self.comm('XP01R'))] def doWriteMccmovement(self, value): return self.comm('XP01S%d' % (self.movementTypes.index(value))) def doReadLinear(self): return int(self.comm('XP48R')) == 1 def doReadIdlecurrent(self): if self.linear: return 0.05 * float(self.comm('XP40R')) # SHOULD BE 0.04 else: return 0.1 * float(self.comm('XP40R')) def doWriteIdlecurrent(self, value): if self.linear: self.comm('XP40S%d' % max(0, min(25, round(value / 0.05)))) # SHOULD BE 0.04 else: self.comm('XP40S%d' % max(0, min(25, round(value / 0.1)))) return self.doReadIdlecurrent() def doReadMovecurrent(self): if self.linear: return 0.05 * float(self.comm('XP41R')) # SHOULD BE 0.04 else: return 0.1 * float(self.comm('XP41R')) def doWriteMovecurrent(self, value): if self.linear: self.comm('XP41S%d' % max(0, min(25, round(value / 0.05)))) # SHOULD BE 0.04 else: self.comm('XP41S%d' % max(0, min(25, round(value / 0.1)))) return self.doReadMovecurrent() def doReadRampcurrent(self): if self.linear: return 0.05 * float(self.comm('XP42R')) # SHOULD BE 0.04 else: return 0.1 * float(self.comm('XP42R')) def doWriteRampcurrent(self, value): if self.linear: self.comm('XP42S%d' % max(0, min(25, round(value / 0.05)))) # SHOULD BE 0.04 else: self.comm('XP42S%d' % max(0, min(25, round(value / 0.1)))) return self.doReadRampcurrent() def doRead(self, maxage=0): return float(self.comm('XP21R')) / (self.slope * self.microstep) def _readSE(self): temp = self.comm('SE') i = ['X', 'Y', 'Z', 'W'].index(self.channel) temp = temp[4 * i:4 * i + 4] return int(temp, 16) def doReadPower(self): if self._readSE() & (1 << 3): return 'on' else: return 'off' def doWritePower(self, value): if value in ['on', 1, True]: self.comm('XMA') else: self.comm('XMD') return self.doReadPower() def doReadMicrostep(self): return int(self.comm('XP45R')) def doWriteMicrostep(self, value): self.comm('XP45S%d' % int(value)) return self.doReadMicrostep() def doReadSpeed(self): return float(self.comm('XP14R')) / float( self.microstep * abs(self.slope)) def doWriteSpeed(self, value): f = max(0, min(40000, value * abs(self.slope) * self.microstep)) self.comm('XP14S%d' % int(f)) return self.doReadSpeed() def doReadAccel(self): return (float(self.comm('XP15R')) / float(self.microstep * abs(self.slope))) def doWriteAccel(self, value): f = max( 4000, min( 500000, 4000 * round( (value * (abs(self.slope) * self.microstep)) / 4000))) self.comm('XP15S%d' % int(f)) return self.doReadAccel() def doStart(self, pos): """go to a absolute postition""" if self.doStatus(0)[0] != status.OK: raise MoveError('Can not start, please check status!') pos = int(pos * self.slope * self.microstep) self.comm('XE%d' % pos) def doStop(self): ''' send the stop command ''' for _i in range(5): if not self.comm('XS'): return self.log.error('Stopping failed! no ACK!') def doSetPosition(self, newpos): ''' set current position to given value''' d = int(newpos * self.slope * self.microstep) self.comm('XP20S%d XP21S%d XP19S%d' % (d, d, d)) # set all counters def doStatus(self, maxage=0): sui = self.comm('SUI')[['X', 'Y', 'Z', 'W'].index(self.channel)] st = int(self.comm('ST')) # for reading enable switch t = self._readSE() sl = [ 'Overcurrent', 'Undervoltage', 'Overtemperature', 'Driver enabled', 'Limit switch - active', 'Limit switch + active', 'stepping error', 'Encoder error', 'Motor halted', 'referenced' ] s = '' sc = status.OK if sui in ['+', '2']: s = s + 'Limit switch + active, ' if sui in ['-', '2']: s = s + 'Limit switch - active, ' for i in range(len(sl)): if t & (1 << i): s = s + sl[i] + ', ' # motor moving -> busy if (t & 0x100) == 0: s = s + 'Motor moving, ' sc = status.BUSY # Overcurrent, Undervoltage, Overtemp or stepping error -> error if (t & 0b1000111) != 0: sc = status.ERROR if st & (1 << 5): s = s + 'Driver enabled externally, ' else: # driver disabled -> error s = s + 'Driver disabled externally, ' sc = status.ERROR if s: s = s[:-2] if sui == '2': # ?both? limit switches touched sc = status.ERROR return sc, s