Exemple #1
0
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)
Exemple #2
0
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)
Exemple #3
0
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)
Exemple #4
0
 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 = {}
Exemple #5
0
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)
Exemple #6
0
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')
Exemple #7
0
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, ''
Exemple #8
0
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
Exemple #9
0
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)
Exemple #10
0
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)
Exemple #11
0
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)
Exemple #12
0
 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)
Exemple #13
0
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