Exemple #1
0
    def state(self):
        if self.is_moving:
            return AxisState("MOVING")

        grp_state = AxisState("READY")
        for i, (name, state) in enumerate([
            (axis.name, axis.state()) for axis in self._axes.itervalues()
        ]):
            if state.MOVING:
                new_state = "MOVING" + " " * i
                grp_state.create_state(
                    new_state,
                    "%s: %s" % (name, grp_state._state_desc["MOVING"]))
                grp_state.set("MOVING")
                grp_state.set(new_state)
            for axis_state in state._current_states:
                if axis_state == "READY":
                    continue
                new_state = axis_state + " " * i
                grp_state.create_state(
                    new_state,
                    "%s: %s" % (name, state._state_desc[axis_state]))
                grp_state.set(new_state)

        return grp_state
Exemple #2
0
def test_custom_state():
    s = AxisState()
    s.set("READY")
    s.create_state("PARKED", "here I am")
    s.set("PARKED")
    assert s.READY
    assert s == "PARKED"
Exemple #3
0
    def test_states(self):
        # empty state
        s = AxisState()
        self.assertEquals(s, "UNKNOWN")

        # moving
        s.set("MOVING")
        self.assertEquals(s, "MOVING")

        # moving => not ready
        self.assertFalse(s.READY)

        # now ready but no more moving
        s.set("READY")
        self.assertTrue(s.READY)
        self.assertFalse(s.MOVING)

        # custom state
        s.create_state("PARKED", "c'est ma place !!")
        s.set("PARKED")
        # still ready
        self.assertTrue(s.READY)
        self.assertEquals(s, "PARKED")

        # Prints string of states.
        self.assertTrue(isinstance(s.current_states(), str))

        # bad name for a state
        self.assertRaises(ValueError, s.create_state, "A bad state")
Exemple #4
0
    def state(self):
        if self.is_moving:
            return AxisState("MOVING")

        grp_state = AxisState("READY")
        for i, (name, state) in enumerate([(axis.name, axis.state()) for axis in self._axes.itervalues()]):
            if state.MOVING:
                new_state = "MOVING"+" "*i
                grp_state.create_state(new_state, "%s: %s" % (name, grp_state._state_desc["MOVING"]))
                grp_state.set("MOVING")
                grp_state.set(new_state) 
            for axis_state in state._current_states:
                if axis_state == "READY":
                    continue
                new_state = axis_state+" "*i
                grp_state.create_state(new_state, "%s: %s" % (name, state._state_desc[axis_state]))     
                grp_state.set(new_state) 

        return grp_state
Exemple #5
0
class Elmo(Controller):
    """
    Elmo motor controller

    configuration example:
    - class: elmo
      udp:
      url: nscopeelmo
      axes:
        - name: rot
          steps_per_unit: 26222.2
          velocity: 377600
          acceleration: 755200
          control_slave: True
    """
    ErrorCode = {
        1: 'Do not Update',
        2: 'Bad Command',
        3: 'Bad Index',
        5: 'BEYOND_ALPHA_BET (Has No Interpreter Meaning',
        6: 'Program is not running',
        7: 'Mode cannot be started - bad initialization data',
        8: 'Motion terminated, probably data underflow',
        9: 'CAN message was lost',
        10: 'Cannot be used by PDO',
        11: 'Cannot write to flash memory',
        12: 'Command not available in this unit mode',
        13: 'Cannot reset communication - UART is busy',
        14: 'Cannot perform CAN NMT service',
        15: 'CAN Life time exceeded - motor shut down',
        16: "The command attribute is array '[]' is expected",
        17: 'Format of UL command is not valid - check the command definition',
        18: 'Empty Assign',
        19: 'Command syntax error',
        21: 'Operand Out of Range',
        22: 'Zero Division',
        23: 'Command cannot be assigned',
        24: 'Bad Operation',
        25: 'Command Not Valid While Moving',
        26: 'Profiler mode not supported in this unit mode (UM)',
        28: 'Out Of Limit',
        29: 'CAN set object return an abort when called from interpreter',
        30: 'No program to continue',
        31: 'CAN get object return an abort when called from interpreter',
        32: 'Communication overrun, parity, noise, or framing error',
        33: 'Bad sensor setting',
        34: 'There is a conflict with another command',
        36:
        'Commutation method (CA[17]) or commutation table does not fit to sensor',
        37: 'Two Or More Hall sensors are defined to the same place',
        38: 'Motion start specified for the past',
        41: 'Command is not supported by this product',
        42: 'No Such Label',
        43: 'CAN state machine in fault(object 0x6041 in DS-402)',
        45: 'Return Error From Subroutine',
        46: 'May Not Use Multi- capture Homing Mode With Stop Event',
        47: 'Program does not exist or not Compiled',
        48: 'Motor cold not start - fault reason in CD',
        50: 'Stack Overflow',
        51: 'Inhibit OR Abort inputs are active, Cannot start motor',
        52: 'PVT Queue Full',
        53: 'Only For Current',
        54: 'Bad Data Base',
        55: 'Bad Context',
        56: 'The product grade does not support this command',
        57: 'Motor Must be Off',
        58: 'Motor Must be On',
        60: 'Bad Unit Mode',
        61: 'Data Base Reset',
        64: 'Cannot set the index of an active table',
        65: 'Disabled By SW',
        66: 'Amplifier Not Ready',
        67: 'Recorder Is Busy',
        68: 'Required profiler mode is not supported',
        69: 'Recorder Usage Error',
        70: 'Recorder data Invalid',
        71: 'Homing is busy',
        72: 'Modulo range must be even',
        73: 'Please Set Position',
        74: 'Bad profile database, see 0x2081 for object number (EE[2])',
        77: 'Buffer Too Large',
        78: 'Out of Program Range',
        80: 'ECAM data inconsistent',
        81: 'Download failed see specific error in EE[3]',
        82: 'Program Is Running',
        83: 'Command is not permitted in a program.',
        84: 'The System Is Not In Point To Point Mode',
        86: 'PVT table is soon going to underflow',
        88: 'ECAM last interval is larger than allowed',
        90: 'CAN state machine not ready (object 0x6041 in DS-402)',
        91: 'Bad PVT Head Pointer',
        92: 'PDO not configured',
        93: 'There is a wrong initiation value for this command',
        95: 'ER[3] Too large for modulo setting applied',
        96: 'User program time out',
        97: 'RS232 receive buffer overflow',
        98: 'Cannot measure current offsets',
        99: 'Bad auxiliary sensor configuration',
        100: 'The requested PWM value is not supported',
        101: 'Absolute encoder setting problem',
        105: 'Speed loop KP out of range',
        106: 'Position loop KP out of range',
        110: 'Too long number',
        111: 'KV vector is invalid',
        112: 'KV defines scheduled block but scheduling is off',
        113: 'Exp task queue is full',
        114: 'Exp task queue is empty',
        115: 'Exp output queue is full',
        116: 'Exp output queue is empty',
        117: 'Bad KV setting for sensor filter',
        118: 'Bad KV setting',
        119: 'Analog Sensor filter out of range',
        120: 'Analog Sensor filter may contain 0 or 2 blocks',
        121: 'Please wait until Analog Sensor initialized',
        122: 'Motion mode is not supported or with initialization conflict',
        123: 'Profiler queue is full',
        125: 'Personality not loaded',
        126: 'User Program failed - variable out of program size',
        127: 'Modulo range must be positive',
        128: 'Bad variable index in database',
        129: 'Variable is not an array',
        130: 'Variable name does not exist',
        131: 'Cannot record local variable',
        132: 'Variable is an array',
        133: 'Number of function input arguments is not as expected',
        134: 'Cannot run local label/function with the XQ command',
        135: 'Frequency identification failed',
        136: 'Not a number',
        137: 'Program already compiled',
        139: 'The number of break points exceeds maximal number',
        140: 'An attempt to set/clear break point at the not relevant line',
        141: 'Boot Identity parameters section is not clear',
        142: 'Checksum of data is not correct',
        143: 'Missing boot identity parameters',
        144: 'Numeric Stack underflow',
        145: 'Numeric stack overflow',
        146: 'Expression stack overflow',
        147: 'Executable command within math expression',
        148: 'Nothing in the expression',
        149: 'Unexpected sentence termination',
        150: 'Sentence terminator not found',
        151: 'Parentheses mismatch',
        152: 'Bad operand type',
        153: 'Overflow in a numeric operator',
        154: 'Address is out of data memory segment',
        155: 'Beyond stack range',
        156: 'Bad op-code',
        157: 'No Available program stack',
        158: 'Out of flash memory range',
        159: 'Flash memory verification error',
        160: 'Program aborted by another thread',
        161: 'Program is not halted.',
        162: 'Badly formatted number.',
        163:
        'There is not enough space in the program data segment. Try to reduce variable usage in the user program.',
        164: 'EC command (not an error)',
        165: 'An attempt was made to access serial flash memory while busy.',
        166: 'Out of modulo range.',
        167: 'Infinite loop in for loop - zero step',
        168: 'Speed too large to start motor.',
        169: 'Time out using peripheral.(overflo w or busy)',
        170: 'Cannot erase sector in flash memory',
        171: 'Cannot read from flash memory',
        172: 'Cannot write to flash memory',
        173: 'Executable area of program is too large',
        174: 'Program has not been loaded',
        175: 'Cannot write program checksum - clear program (CP)',
        176: 'User code, variables and functions are too large',
        181: 'Writing to Flash program area, failed',
        182: 'PAL Burn Is In Process or no PAL is burned',
        183: 'PAL Burn (PB Command) Is Disabled',
        184: 'Capture option already used by other operation',
        185:
        'This element may be modified only when interpolation is not active',
        186: 'Interpolation queue is full',
        187: 'Incorrect Interpolation sub-mode',
        188: 'Gantry slave is disabled',
    }

    def __init__(self, *args, **kwargs):
        Controller.__init__(self, *args, **kwargs)
        self._cnx = None

    def initialize(self):
        config = self.config.config_dict
        if get_comm_type(config) == UDP:
            opt = {'port': 5001, 'eol': ';'}
        else:  # SERIAL
            opt = {'baudrate': 115200, 'eol': ';'}

        self._cnx = get_comm(config, **opt)
        self._elmostate = AxisState()
        for state, human in (('SLAVESWITCH', 'Slave switch activate'),
                             ('INHIBITSWITCH', 'Inhibit switch active'),
                             ('CLOSEDLOOPOPEN', 'Closed loop open'),
                             ('DRIVEFAULT', 'Problem into the drive')):
            self._elmostate.create_state(state, human)

    def initialize_hardware(self):
        # Check that the controller is alive
        try:
            self._query("VR\r", timeout=50e-3)
        except SocketTimeout:
            raise RuntimeError("Controller Elmo (%s) is not connected" % \
                               (self._cnx._host))

    def initialize_axis(self, axis):
        axis._mode = Cache(axis, "mode", default_value=None)

    def initialize_hardware_axis(self, axis):
        # Check user-mode
        mode = int(self._query("UM"))
        asked_mode = axis.config.get('user_mode', int, 5)
        if (mode != asked_mode):
            self._query("UM=%d" % asked_mode)
        # Check closed loop on
        if self._query("MO") != '1':
            self.set_on(axis)
        mode = self._query("UM")
        axis._mode.value = int(mode)

    def set_on(self, axis):
        self._set_power(axis, True)

    def set_off(self, axis):
        self._set_power(axis, False)

    def _set_power(self, axis, activate):
        # Activate slave if needed
        if axis.config.get('control_slave', bool, False):
            self._query("OB[1]=%d" % activate)

        self._query("MO=%d" % activate)

    def _query(self, msg, in_error_code=False, **keys):
        send_message = msg + '\r'
        raw_reply = self._cnx.write_readline(send_message, **keys)
        if not raw_reply.startswith(send_message):  # something weird happened
            self._cnx.close()
            raise RuntimeError("received reply: %s\n"
                               "expected message starts with %s" % msg)
        reply = raw_reply[len(send_message):]
        if not in_error_code and reply.endswith('?'):
            error_code = self._query("EC", in_error_code=True)
            try:
                error_code = int(error_code)
            except ValueError:  # Weird, don't know what to do
                pass
            else:
                human_error = self.ErrorCode.get(error_code, 'Unknown')
                raise RuntimeError("Error %d (%s), Query (%s)" %
                                   (error_code, human_error, msg))
        return reply

    def start_jog(self, axis, velocity, direction):
        self._query("JV=%d" % velocity * direction)
        self._query("BG")

    def stop_jog(self, axis):
        self._query("JV=0")
        self._query("BG")
        #check if sync_hard needed

    def read_position(self, axis):
        if axis._mode == 2:
            return float(self._query("PX"))
        else:
            return float(self._query("DV[3]"))

    def set_position(self, axis, new_pos):
        pos = round(new_pos)
        self._set_power(axis, False)
        encodeur_name = "PY" if axis._mode == 4 else "PX"
        self._query("%s=%d" % (encodeur_name, pos))
        self._set_power(axis, True)
        self._query("PA=%d" % pos)
        return self.read_position(axis)

    def read_acceleration(self, axis):
        return int(self._query("AC"))

    def set_acceleration(self, axis, new_acc):
        self._query("AC=%d" % new_acc)
        self._query("DC=%d" % new_acc)
        return self.read_acceleration(axis)

    def read_velocity(self, axis):
        return float(self._query("SP"))

    def set_velocity(self, axis, new_vel):
        self._query("SP=%d" % new_vel)
        return self.read_velocity(axis)

    def home_search(self, axis, switch, set_pos=None):
        #can't set the position when searching home
        #we should change emotion to add an option in
        #home search i.e: set_pos = None
        if set_pos is not None:
            commands = [
                "HM[3]=3",
                "HM[2]=%d" % round(set_pos), "HM[4]=0", "HM[5]=0", "HM[1]=1"
            ]
        else:
            commands = ["HM[3]=3", "HM[4]=0", "HM[5]=2", "HM[1]=1"]
        for cmd in commands:
            self._query(cmd)
        #Start search
        step_sign = 1 if axis.config.get('steps_per_unit', float) > 0 else -1
        self._query("JV=%d", switch * step_sign * self.read_velocity())
        self._query("BG")

    def state(self, axis):
        state = self._elmostate.new()

        # check first that the controller is ready to move
        # bit0 of Status Register (page 3.135)
        ans = int(self._query("SR"))
        if (ans & (1 << 7)):
            state.set("MOVING")
        if (ans & 0x1):  # problem into the drive
            state.set("DRIVEFAULT")
        if not (ans & (1 << 4)):  # closed loop open
            state.set("CLOSEDLOOPOPEN")

        # Check limits
        ans = int(self._query("IP"))
        if axis.config.get('control_slave', bool, False):
            if ans & (1 << 17):
                state.set("SLAVESWITCH")

        if ans & (1 << 6):
            state.set("LIMPOS")
        if ans & (1 << 7):
            state.set("LIMNEG")
        if ans & (1 << 8):
            #should be checked in spec ends in MOT_EMERGENCY ?
            state.set("INHIBITSWITCH")

        # Check motion state
        # Wrong if homing
        #
        ans = int(self._query("MS"))
        if ans == 0:
            state.set("READY")
        elif ans == 1 or ans == 2:
            state.set("MOVING")
        elif ans == 3:
            state.set("FAULT")

        return state

    def start_one(self, motion):
        # check first that the controller is ready to move
        # bit0 of Status Register (page 3.135)
        ans = int(self._query("SR"))
        if (ans & 0x1):
            raise RuntimeError("problem into the drive")
        if not (ans & (1 << 4)):
            raise RuntimeError("closed loop open")

        self._query("PA=%d" % round(motion.target_pos))
        self._query("BG")

    def stop(self, axis):
        self._query("ST")
        #todo spec macros check if motor is in homing phase...

    @object_method(types_info=("None", "int"))
    def get_user_mode(self, axis):
        return int(self._query("UM"))

    @object_method(types_info=("int", "int"))
    def set_user_mode(self, axis, mode):
        commands = ["MO=0", "UM=%d" % mode]
        if mode == 2:
            commands.append("PM=1")
        commands.append("MO=1")
        for cmd in commands:
            self._query(cmd)
        if mode == 5 or mode == 4:
            self.sync_hard()
        mode = int(self._query("UM"))
        axis._mode.value = mode
        return mode

    @object_method(types_info=("None", ("float", "float")))
    def jog_range(self, axis):
        #this method should be in emotion
        #todo move it has a generic
        return float(self._query("VL[2]")), float(self._query("VH[2]"))

    @object_method(types_info=("None", "bool"))
    def get_enable_slave(self, axis):
        return bool(self._query("OB[1]"))

    @object_method(types_info=("bool", "bool"))
    def set_enable_slave(self, axis, active):
        self._query("OB[1]=%d" % active)
        return bool(self._query("OB[1]"))

    #encoders
    def initialize_encoder(self, encoder):
        pass

    def read_encoder(self, encoder):
        return float(self._query("PX"))

    def set_encoder(self, encoder, steps):
        self._query("PX=%d" % steps)
Exemple #6
0
class Icepap(Controller):
    """
    IcePAP stepper controller without Deep Technology of Communication.
    But if you prefer to have it (DTC) move to IcePAP controller class.
    Use this class controller at your own risk, because you won't
    have any support...
    """
    STATUS_DISCODE = {
        0 : ('POWERENA',      'power enabled'),                   
        1 : ('NOTACTIVE',     'axis configured as not active'),   
        2 : ('ALARM',         'alarm condition'),                 
        3 : ('REMRACKDIS',    'remote rack disable input signal'),
        4 : ('LOCRACKDIS',    'local rack disable switch'),       
        5 : ('REMAXISDIS',    'remote axis disable input signal'),
        6 : ('LOCAXISDIS',    'local axis disable switch'),       
        7 : ('SOFTDIS',       'software disable'),
    }

    STATUS_MODCODE = {
        0 : ('OPER',          'operation mode'),     
        1 : ('PROG',          'programmation mode'), 
        2 : ('TEST',          'test mode'),          
        3 : ('FAIL',          'fail mode'),
    }
    STATUS_STOPCODE = {
        0 : ('SCEOM',         'end of movement'),                 
        1 : ('SCSTOP',        'last motion was stopped'),         
        2 : ('SCABORT',       'last motion was aborted'),         
        3 : ('SCLIMPOS',      'positive limitswitch reached'),    
        4 : ('SCLINNEG',      'negative limitswitch reached'),    
        5 : ('SCSETTLINGTO',  'settling timeout'),                
        6 : ('SCAXISDIS',     'axis disabled (no alarm)'),        
        7 : ('SCBIT7',        'n/a'),                             
        8 : ('SCINTFAIL',     'internal failure'),                
        9 : ('SCMOTFAIL',     'motor failure'),                   
        10 : ('SCPOWEROVL',   'power overload'),                  
        11 : ('SCHEATOVL',    'driver overheating'),              
        12 : ('SCCLERROR',    'closed loop error'),               
        13 : ('SCCENCERROR',  'control encoder error'),           
        14 : ('SCBIT14',      'n/a'),                             
        15 : ('SCEXTALARM',   'external alarm'),
    }

    def __init__(self,*args,**kwargs):
        Controller.__init__(self,*args,**kwargs)
        self._cnx = None
        self._last_axis_power_time = dict()

    def initialize(self):
        hostname = self.config.get("host")
        self._cnx = Command(hostname,5000,eol='\n')

        self._icestate = AxisState()
        self._icestate.create_state("POWEROFF", "motor power is off")
        for codes in (self.STATUS_DISCODE,self.STATUS_MODCODE,self.STATUS_STOPCODE):
            for state,desc in codes.values():
                self._icestate.create_state(state,desc)

    def finalize(self):
        if self._cnx is not None:
            self._cnx.close()
            
    def initialize_axis(self,axis):
        axis.address = axis.config.get("address",lambda x: x)

        if hasattr(axis,'_init_software'):
            axis._init_software()

    def initialize_hardware_axis(self,axis):
        if axis.config.get('autopower', converter=bool, default=True):
            try:
                self.set_on(axis)
            except:
                sys.excepthook(*sys.exc_info())

        if hasattr(axis,'_init_hardware'):
            axis._init_hardware()

    #Axis power management 
    def set_on(self,axis):
        """
        Put the axis power on
        """
        self._power(axis,True)

    def set_off(self,axis):
        """
        Put the axis power off
        """
        self._power(axis,False)

    def _power(self,axis,power):
        _ackcommand(self._cnx,"POWER %s %s" % 
                    ("ON" if power else "OFF",axis.address))
        self._last_axis_power_time[axis] = time.time()

    def read_position(self,axis,cache=True):
        pos_cmd = "FPOS" if cache else "POS"
        return int(_command(self._cnx,"?%s %s" % (pos_cmd,axis.address)))
    
    def set_position(self,axis,new_pos):
        if isinstance(axis,SlaveAxis):
            pre_cmd = "%d:DISPROT LINKED;" % axis.address
        else:
            pre_cmd = None
        _ackcommand(self._cnx,"POS %s %d" % (axis.address,int(round(new_pos))),
                    pre_cmd = pre_cmd)
        return self.read_position(axis,cache=False)

    def read_velocity(self,axis):
        return float(_command(self._cnx,"?VELOCITY %s" % axis.address))

    def set_velocity(self,axis,new_velocity):
        _ackcommand(self._cnx,"VELOCITY %s %f" % 
                    (axis.address,new_velocity))
        return self.read_velocity(axis)

    def read_acceleration(self,axis):
        acctime = float(_command(self._cnx,"?ACCTIME %s" % axis.address))
        velocity = self.read_velocity(axis)
        return velocity/float(acctime)

    def set_acceleration(self,axis,new_acc):
        velocity = self.read_velocity(axis)
        new_acctime = velocity/new_acc

        _ackcommand(self._cnx,"ACCTIME %s %f" % (axis.address,new_acctime))
        return self.read_acceleration(axis)

    def state(self,axis):
        last_power_time = self._last_axis_power_time.get(axis,0)
        if time.time() - last_power_time < 1.:
            status_cmd = "?STATUS"
        else:
            self._last_axis_power_time.pop(axis,None)
            status_cmd = "?FSTATUS"

        status = int(_command(self._cnx,"%s %s" %
                              (status_cmd,axis.address)),16)
        status ^= 1<<23 #neg POWERON FLAG
        state = self._icestate.new()
        for mask,value in (((1<<9),"READY"),
                           ((1<<10|1<<11),"MOVING"),
                           ((1<<18),"LIMPOS"),
                           ((1<<19),"LIMNEG"),
                           ((1<<20),"HOME"),
                           ((1<<23),"POWEROFF")):
            if status & mask:
                state.set(value)

        state_mode = (status >> 2) & 0x3
        if state_mode:
            state.set(self.STATUS_MODCODE.get(state_mode)[0])

        stop_code = (status >> 14) & 0xf
        if stop_code:
            state.set(self.STATUS_STOPCODE.get(stop_code)[0])

        disable_condition = (status >> 4) & 0x7
        if disable_condition:
            state.set(self.STATUS_DISCODE.get(disable_condition)[0])

        if state.READY:
            #if motor is ready then no need to investigate deeper
            return state

        if not state.MOVING:
            # it seems it is not safe to call warning and/or alarm commands
            # while homing motor, so let's not ask if motor is moving
            if status & (1<<13):
                try:
                    warning = _command(self._cnx,"%d:?WARNING" % axis.address)
                except TypeError:
                    pass
                else:
                    warn_str =  "warning condition: \n" + warning
                    status.create_state("WARNING",warn_str)
                    status.set("WARNING")

            try:
                alarm = _command(self._cnx,"%d:?ALARM" % axis.address)
            except (RuntimeError,TypeError):
                pass
            else:
                if alarm != "NO":
                    alarm_dsc = "alarm condition: " + str(alarm)
                    state.create_state("ALARMDESC",alarm_dsc)
                    state.set("ALARMDESC")

        return state

    def get_info(self,axis):
        pre_cmd = '%s:' % axis.address
        r =  "MOTOR   : %s\n" % axis.name
        r += "SYSTEM  : %s (ID: %s) (VER: %s)\n" % (self._cnx._host,
                                                    _command(self._cnx,"0:?ID"),
                                                    _command(self._cnx,"?VER"))
        r += "DRIVER  : %s\n" % axis.address
        r += "POWER   : %s\n" % _command(self._cnx,pre_cmd + "?POWER")
        r += "CLOOP   : %s\n" % _command(self._cnx,pre_cmd + "?PCLOOP")
        r += "WARNING : %s\n" % _command(self._cnx,pre_cmd + "?WARNING")
        r += "ALARM   : %s\n" % _command(self._cnx,pre_cmd + "?ALARM")
        return r
    
    def raw_write(self,message,data = None):
        return _command(self._cnx,message,data)
        
    def raw_write_read(self,message,data = None):
        return _ackcommand(self._cnx,message,data)

    def prepare_move(self,motion):
        pass

    def start_one(self,motion):
        if isinstance(motion.axis,SlaveAxis):
            pre_cmd = "%d:DISPROT LINKED;" % motion.axis.address
        else:
            pre_cmd = None

        _ackcommand(self._cnx,"MOVE %s %d" % (motion.axis.address,
                                              motion.target_pos),
                    pre_cmd = pre_cmd)

    def start_all(self,*motions):
        if motions > 1:
            cmd = "MOVE GROUP "
            cmd += ' '.join(["%s %d" % (m.axis.address,m.target_pos) for m in motions])
            _ackcommand(self._cnx,cmd)
        elif motions:
            self.start_one(motions[0])

    def stop(self,axis):
        _command(self._cnx,"STOP %s" % axis.address)

    def stop_all(self,*motions):
        for motion in motions:
            self.stop(motion.axis)

    def home_search(self,axis,switch):
        cmd = "HOME " + ("+1" if switch > 0 else "-1")
        _ackcommand(self._cnx,"%s:%s" % (axis.address,cmd))
        # IcePAP status is not immediately MOVING after home search command is sent
        gevent.sleep(0.2)

    def home_state(self,axis):
        s = self.state(axis)
        if s != 'READY' and s != 'POWEROFF':
             s.set('MOVING')
        return s

    def limit_search(self,axis,limit):
        cmd = "SRCH LIM" + ("+" if limit>0 else "-")
        _ackcommand(self._cnx,"%s:%s" % (axis.address,cmd))
        # TODO: MG18Nov14: remove this sleep (state is not immediately MOVING)
        gevent.sleep(0.1)

    def initialize_encoder(self,encoder):
        # Get axis config from bliss config
        # address form is XY : X=rack {0..?} Y=driver {1..8}
        encoder.address = encoder.config.get("address", int)

        # Get optional encoder input to read
        enctype = encoder.config.get("type",str,"ENCIN").upper()
        # Minium check on encoder input
        if enctype not in ['ENCIN', 'ABSENC', 'INPOS', 'MOTOR', 'AXIS', 'SYNC']:
            raise ValueError('Invalid encoder type')
        encoder.enctype = enctype

    def read_encoder(self,encoder):
        value = _command(self._cnx,"?ENC %s %d" % (encoder.enctype,encoder.address))
        return int(value)

    def set_encoder(self,encoder,steps):
        _ackcommand(self._cnx,"ENC %s %d %d" % 
                    (encoder.enctype,encoder.address,steps))

    def set_event_positions(self,axis_or_encoder,positions):
        int_position = numpy.array(positions,dtype=numpy.int32)
        #position has to be ordered
        int_position.sort()
        address = axis_or_encoder.address
        if not len(int_position):
            _ackcommand(self._cnx,"%s:ECAMDAT CLEAR" % address)
            return

        if isinstance(axis_or_encoder,Axis):
            source = 'AXIS'
        else:                   # encoder
            source = 'MEASURE'

        #load trigger positions
        _ackcommand(self._cnx,"%s:*ECAMDAT %s DWORD" % (address,source),
                    int_position)
         # send the trigger on the multiplexer
        _ackcommand(self._cnx,"%s:SYNCAUX eCAM" % address)

    def get_event_positions(self,axis_or_encoder):
        """
        For this controller this method should be use
        for debugging purposed only... 
        """
        address = axis_or_encoder.address
        #Get the number of positions
        reply = _command(self._cnx,"%d:?ECAMDAT" % address)
        reply_exp = re.compile("(\w+) +([+-]?\d+) +([+-]?\d+) +(\d+)")
        m = reply_exp.match(reply)
        if m is None:
            raise RuntimeError("Reply Didn't expected: %s" % reply)
        source = m.group(1)
        nb = int(m.group(4))
        
        if isinstance(axis_or_encoder,Axis):
            nb = nb if source == 'AXIS' else 0
        else:                   # encoder
            nb = nb if source == "MEASURE" else 0

        positions = numpy.zeros((nb,),dtype = numpy.int32)
        if nb > 0:
            reply_exp = re.compile(".+: +([+-]?\d+)")
            reply = _command(self._cnx,"%d:?ECAMDAT %d" % (address,nb))
            for i,line in enumerate(reply.split('\n')):
                m = reply_exp.match(line)
                if m:
                    pos = int(m.group(1))
                    positions[i] = pos
        return positions

    def get_linked_axis(self):
        reply = _command(self._cnx,"?LINKED")
        linked = dict()
        for line in reply.strip().split('\n'):
            values = line.split()
            linked[values[0]] = [int(x) for x in values[1:]]
        return linked

    @object_method(types_info=("bool","bool"))
    def activate_closed_loop(self,axis,active):
        _command(self._cnx,"#%s:PCLOOP %s" % (axis.address,"ON" if active else "OFF"))
        return active

    @object_method(types_info=("None","bool"))
    def is_closed_loop_activate(self,axis):
        return True if _command(self._cnx,"%s:?PCLOOP" % axis.address) == 'ON' else False

    @object_method(types_info=("None","None"))
    def reset_closed_loop(self,axis):
        measure_position = int(_command(self._cnx,"%s:?POS MEASURE" % axis.address))
        self.set_position(axis,measure_position)
        if axis.config.get('autopower', converter=bool, default=True):
            self.set_on(axis)
        axis.sync_hard()
        
    @object_method(types_info=("None","int"))
    def temperature(self,axis):
        return int(_command(self._cnx,"%s:?MEAS T" % axis.address))

    @object_method(types_info=(("float","bool"),"None"))
    def set_tracking_positions(self,axis,positions,cyclic = False):
        """
        Send position to the controller which will be tracked.

        positions --  are expressed in user unit
        cyclic -- cyclic position or not default False

        @see activate_track method
        """
        address = axis.address
        if not len(positions):
            _ackcommand(self._cnx,"%s:LISTDAT CLEAR" % address)
            return

        dial_positions = axis.user2dial(numpy.array(positions, dtype=numpy.float))
        step_positions = numpy.array(dial_positions * axis.steps_per_unit,
                                     dtype=numpy.int32)
        _ackcommand(self._cnx,"%d:*LISTDAT %s DWORD" % 
                    (address, "CYCLIC" if cyclic else "NOCYCLIC"),
                    step_positions)

    @object_method(types_info=("None",("float","bool")))
    def get_tracking_positions(self,axis):
        """
        Get the tacking positions.
        This method should only be use for debugging
        return a tuple with (positions,cyclic flag)
        """
        address = axis.address
        #Get the number of positions
        reply = _command(self._cnx,"%d:?LISTDAT" % address)
        reply_exp = re.compile("(\d+) *(\w+)?")
        m = reply_exp.match(reply)
        if m is None:
            raise RuntimeError("Reply didn't expected: %s" % reply)
        nb = int(m.group(1))
        positions = numpy.zeros((nb,),dtype = numpy.int32)
        cyclic = True if m.group(2) == "CYCLIC" else False
        if nb > 0:
            reply_exp = re.compile(".+: +([+-]?\d+)")
            reply = _command(self._cnx,"%d:?LISTDAT %d" % (address,nb))
            for i,line in enumerate(reply.split('\n')):
                m = reply_exp.match(line)
                if m:
                    pos = int(m.group(1))
                    positions[i] = pos
            dial_positions = positions / axis.steps_per_unit
            positions = axis.dial2user(dial_positions)
        return positions,cyclic

    @object_method(types_info=(("bool","str"),"None"))
    def activate_tracking(self,axis,activate,mode = None):
        """
        Activate/Deactivate the tracking position depending on
        activate flag
        mode -- default "INPOS" if None.
        mode can be :
           - SYNC   -> Internal SYNC signal
           - ENCIN  -> ENCIN signal
           - INPOS  -> INPOS signal
           - ABSENC -> ABSENC signal
        """
        address = axis.address

        if not activate:
            _ackcommand(self._cnx,"STOP %d" % address)
            axis.sync_hard()
        else:
            if mode is None: mode = "INPOS"
            possibles_modes = ["SYNC","ENCIN","INPOS","ABSENC"]
            if mode not in possibles_modes:
                raise ValueError("mode %s is not managed, can only choose %s" % 
                                 (mode,possibles_modes))
            if mode == "INPOS":
                _ackcommand(self._cnx, "%d:POS INPOS 0" % address)
            _ackcommand(self._cnx,"%d:LTRACK %s" % (address,mode))
        
    @object_method(types_info=("float", "None"))
    def blink(self, axis, second=3.):
        """
        Blink axis driver
        """
        _command(self._cnx,"%d:BLINK %f" % (axis.address, second))

    def reset(self):
        _command(self._cnx,"RESET")

    def mdspreset(self):
        """
        Reset the MASTER DSP
        """
        _command(self._cnx,"_dsprst")

    def reboot(self):
        _command(self._cnx,"REBOOT")
        self._cnx.close()
Exemple #7
0
class IcePAP(Controller):

    """Implement IcePAP stepper motor controller access"""
    default_group    = None
    default_groupenc = None

    def __init__(self, name, config, axes, encoders):
        """Contructor"""
        Controller.__init__(self, name, config, axes, encoders)

        self.libdevice = None

    def initialize(self):
        """Controller initialization"""
        self.log_info("initialize() called")

        # Get controller config from bliss config
        # Mandatory parameters (port number is not needed)
        self.host = self.config.get("host")

        # Optional parameters
        try:
            self.libdebug = int(self.config.get("libdebug"))
        except:
            self.libdebug = 1

        # Create an IcePAP lib object to access the MASTER
        self.libdevice = libicepap.System(
            self.host,
            "verb=%d" %
            self.libdebug)

        # Create an IcePAP lib object as default group
        if IcePAP.default_group is None:
            IcePAP.default_group = libicepap.Group("default")
        self.libgroup = IcePAP.default_group

        # Create an IcePAP lib object as default group for encoders
        if IcePAP.default_groupenc is None:
            IcePAP.default_groupenc = libicepap.Group("default_enc")
        self.libgroupenc = IcePAP.default_groupenc

        # Create custom EMotion states to map IcePAP status bits
        self.icestate = AxisState()
        self.icestate.create_state("POWEROFF", "motor power is off")
        for code in libicepap.STATUS_DISCODE_STR:
            self.icestate.create_state(
                libicepap.STATUS_DISCODE_STR[code],
                libicepap.STATUS_DISCODE_DSC[code])
        for code in libicepap.STATUS_MODCODE_STR:
            self.icestate.create_state(
                libicepap.STATUS_MODCODE_STR[code],
                libicepap.STATUS_MODCODE_DSC[code])
        for code in libicepap.STATUS_STOPCODE_STR:
            self.icestate.create_state(
                libicepap.STATUS_STOPCODE_STR[code],
                libicepap.STATUS_STOPCODE_DSC[code])

    def finalize(self):
        """Controller no more needed"""
        self.log_info("finalize() called")

        # Remove any group in the IcePAP lib
        try:
            self.libgroup.delete()
            self.libgroupenc.delete()
        except:
            pass

        # Close IcePAP lib socket/threads
        if self.libdevice is not None:
            self.libdevice.close()
        

    def initialize_axis(self, axis):
        """Axis initialization"""
        self.log_info("initialize_axis() called for axis %r" % axis.name)

        # Get axis config from bliss config
        # address form is XY : X=rack {0..?} Y=driver {1..8}
        axis.address = axis.config.get("address", int)

        # Create an IcePAP lib axis object
        device = self.libdevice
        address = axis.address
        name = axis.name
        axis.libaxis = libicepap.Axis(device, address, name)

        # Add the axis to the default IcePAP lib group
        self.libgroup.add_axis(axis.libaxis)

        # Initialiaze hardware
        # if set_power fails, display exception but let axis
        # be created properly
        try:
            self.libgroup.set_power(libicepap.ON, axis.libaxis)
        except:
            sys.excepthook(*sys.exc_info())

        # Add new axis oject methods
        add_axis_method(axis, self.get_identifier, types_info=("None", "str"))

    def set_on(self, axis):
        """Switch power on"""
        self.libgroup.set_power(libicepap.ON, axis.libaxis)

    def set_off(self, axis):
        """Switch power off"""
        self.libgroup.set_power(libicepap.OFF, axis.libaxis)

    def read_position(self, axis):
        """Returns axis position in motor units"""
        self.log_info("position() called for axis %r" % axis.name)
        return self.libgroup.pos(axis.libaxis)


    def set_position(self, axis, new_pos):
        """Set axis position to a new value given in motor units"""
        l = libicepap.PosList()
        l[axis.libaxis] = new_pos
        self.libgroup.pos(l)
        return self.read_position(axis)


    def read_velocity(self, axis):
        """Returns axis current velocity in user units/sec"""
        #TODO: wouldn't be better in steps/s ?
        return self.libgroup.velocity(axis.libaxis)


    def set_velocity(self, axis, new_velocity):
        """Set axis velocity given in units/sec"""
        self.log_info("set_velocity(%fstps/sec) called for axis %r" %
                      (new_velocity, axis.name))

        l = libicepap.VelList()
        l[axis.libaxis] = new_velocity
        self.libgroup.velocity(l)

        # Always return the current velocity
        return self.read_velocity(axis)


    def read_acceleration(self, axis):
        """Returns axis current acceleration in steps/sec2"""
        acctime  = self.libgroup.acctime(axis.libaxis)
        velocity = self.read_velocity(axis)
        return velocity/acctime

    def set_acceleration(self, axis, new_acc):
        """Set axis acceleration given in steps/sec2"""
        self.log_info("set_acceleration(%fstps/sec2) called for axis %r" %
            (new_acc, axis.name))
        velocity     = self.read_velocity(axis)
        new_acctime  = velocity/new_acc

        self.log_info("set_acctime(%fsec) called for axis %r" %
                      (new_acctime, axis.name))
        l = libicepap.AcctimeList()
        l[axis.libaxis] = new_acctime
        self.libgroup.acctime(l)

        # Always return the current acceleration
        return self.read_acceleration(axis)

    def state(self, axis):
        """Returns the current axis state"""
        self.log_info("state() called for axis %r" % axis.name)

        # The axis can only be accessed through a group in IcePAP lib
        # Use the default group
        status = self.libgroup.status(axis.libaxis)

        # Convert status from icepaplib to bliss format.
        self.icestate.clear()

        # first all concurrent states
        if(libicepap.status_lowlim(status)):
            self.icestate.set("LIMNEG")

        if(libicepap.status_highlim(status)):
            self.icestate.set("LIMPOS")

        if(libicepap.status_home(status)):
            self.icestate.set("HOME")

        modcod, modstr, moddsc = libicepap.status_get_mode(status)
        if modcod != None:
            self.icestate.set(modstr)

        sccod, scstr, scdsc = libicepap.status_get_stopcode(status)
        if sccod != None:
            self.icestate.set(scstr)

        if(libicepap.status_isready(status)):
            self.icestate.set("READY")
            # if motor is ready then no need to investigate deeper
            return self.icestate

        if(libicepap.status_ismoving(status)):
            self.icestate.set("MOVING")

        if(not libicepap.status_ispoweron(status)):
            self.icestate.set("POWEROFF")

        discod, disstr, disdsc = libicepap.status_get_disable(status)
        if discod != None:
            self.icestate.set(disstr)

        if not self.icestate.MOVING:
          # it seems it is not safe to call warning and/or alarm commands
          # while homing motor, so let's not ask if motor is moving
          if(libicepap.status_warning(status)):
              warn_str = self.libgroup.warning(axis.libaxis)
              warn_dsc = "warning condition: \n" + str(warn_str)
              self.icestate.create_state("WARNING",  warn_dsc)
              self.icestate.set("WARNING")

          alarm_str = self.libgroup.alarm(axis.libaxis)
          if alarm_str != 'NO':
              alarm_dsc = "alarm condition: " + str(alarm_str)
              self.icestate.create_state("ALARMDESC",  alarm_dsc)
              self.icestate.set("ALARMDESC")

        return self.icestate

    def prepare_move(self, motion):
        """
        Called once before a single axis motion,
        positions in motor units
        """
        self.log_info("prepare_move(%fstps) called for axis %r" %
            (motion.target_pos, motion.axis.name))
        pass

    def start_one(self, motion):
        """
        Called on a single axis motion,
        returns immediately,
        positions in motor units
        """
        self.log_info("start_one(%fsteps) called for axis %r" % 
            (motion.target_pos, motion.axis.name))
        target_positions = libicepap.PosList()
        target_positions[motion.axis.libaxis] = motion.target_pos
        self.libgroup.move(target_positions)

    def start_all(self, *motion_list):
        """
        Called once per controller with all the axis to move
        returns immediately,
        positions in motor units
        """
        self.log_info("start_all() called")
        target_positions = libicepap.PosList()
        for motion in motion_list:
            target_positions[motion.axis.libaxis] = motion.target_pos
        self.libgroup.move(target_positions)

    def stop(self, axis):
        """Stops smoothly an axis motion"""
        self.log_info("stop() called for axis %r" % axis.name)
        self.libgroup.stop(axis.libaxis)

    def stop_all(self, *motion_list):
        """Stops smoothly all the moving axis given"""
        self.log_info("stop_all() called")
        axis_list = []
        for motion in motion_list:
            axis_list.append(motion.axis.libaxis)
        self.libgroup.stop(axis_list)

    def home_search(self, axis, switch):
        """Launch a homing sequence"""
        cmd = "HOME " + ("+1" if switch > 0 else "-1")
        # TODO: MP17Nov14: missing argin on position to set at home
        # TODO: MP17Nov14: missing home search in IcePAP library
        self.libgroup.ackcommand(cmd, axis.libaxis)
        # IcePAP status is not immediately MOVING after home search command is sent
        time.sleep(0.2)

    def home_state(self, axis):
        """Returns the current axis state while homing"""
        return self.state(axis)

    def limit_search(self, axis, limit):
        """
        Launch a limitswitch search sequence
        the sign of the argin gives the search direction
        """
        cmd = "SRCH"
        if limit>0:
            cmd += " LIM+"
        else:
            cmd += " LIM-"
        
        # TODO: MP17Nov14: missing limit search in IcePAP library
        self.libgroup.ackcommand(cmd, axis.libaxis)
        # TODO: MG18Nov14: remove this sleep (state is not immediately MOVING)
        time.sleep(0.1)

    def initialize_encoder(self, encoder):
        """Encoder initialization"""
        self.log_info("initialize_encoder() called for axis %r" % encoder.name)

        # Get axis config from bliss config
        # address form is XY : X=rack {0..?} Y=driver {1..8}
        encoder.address = encoder.config.get("address", int)

        # Get optional encoder input to read
        try:
            enctype = string.upper(encoder.config.get("type"))
        except:
            enctype = "ENCIN"

        # Minium check on encoder input
        if enctype not in ['ENCIN', 'ABSENC', 'INPOS', 'MOTOR']:
            raise ValueError('Invalid encoder type')
        encoder.enctype = enctype

        # Create an IcePAP lib axis object for each encoder
        # as there is no encoder objects in the lib
        device  = self.libdevice
        address = encoder.address
        name    = encoder.name
        encoder.libaxis = libicepap.Axis(device, address, name)

        # Add the axis to the default IcePAP lib group
        self.libgroupenc.add_axis(encoder.libaxis)

    def read_encoder(self, encoder):
        """Returns encoder position in encoder units"""
        self.log_info("read_encoder() called for encoder %r" % encoder.name)

        # Prepare the command to acces directly the encoder input
        cmd = ' '.join(['?ENC', encoder.enctype])
        ret = self.libgroupenc.command(cmd, encoder.libaxis)

        # Return encoder steps
        return int(ret)

    def set_encoder(self, encoder, steps):
        """Set encoder position to a new value given in encoder units"""
        self.log_info("set_encoder(%f) called for encoder %r" % 
            (steps, encoder.name))

        # Prepare the command to acces directly the encoder input
        cmd = ' '.join(['ENC', encoder.enctype, '%d'%steps])
        self.libgroupenc.ackcommand(cmd, encoder.libaxis)

        # No need to return the current encoder position
        return

    def log_level(self, lvl):
        """Changes IcePAP and eMotion libraries verbose level"""

        # Change in the eMotion library
        log.level(lvl)

        # Value mapping between the two libraries
        #        eMotion == IcePAP
        #   NOTSET ==  0 == 0 == DBG_NONE
        #   DEBUG  == 10 == 4 == DBG_DATA
        #   INFO   == 20 == 2 == DBG_TRACE
        #   WARNING== 30 ==
        #   ERROR  == 40 == 1 == DBG_ERROR
        #   CRITIC == 50 ==
        #
        val = {
            log.NOTSET: 0,
            log.DEBUG: 4,
            log.INFO: 2,
            log.ERROR: 1,
        }[lvl]

        # Change in the IcePAP library
        self.libdevice.set_verbose(val)

        # Always return the current eMotion level
        self.log_info("log_level(%s) called, lib(%d)" %
                      (log.getLevelName(lvl), val))
        return log.level()

    def log_error(self, msg):
        """Logging method"""
        log.error(_ICEPAP_TAB + msg)

    def log_info(self, msg):
        """Logging method"""
        log.info(_ICEPAP_TAB + msg)

    def get_identifier(self, axis):
        """Returns the unique string identifier of the specified axis"""
        self.log_info("get_identifier() called for axis %r" % axis.name)
        return self.libgroup.command("?ID", axis.libaxis)
Exemple #8
0
class Elmo(Controller):
    """
    Elmo motor controller

    configuration example:
    - class: elmo
      udp:
      url: nscopeelmo
      axes:
        - name: rot
          steps_per_unit: 26222.2
          velocity: 377600
          acceleration: 755200
          control_slave: True
    """
    ErrorCode = {
        1 : 'Do not Update',
        2 : 'Bad Command',
        3 : 'Bad Index',
        5 : 'BEYOND_ALPHA_BET (Has No Interpreter Meaning',
        6 : 'Program is not running',
        7 : 'Mode cannot be started - bad initialization data',
        8 : 'Motion terminated, probably data underflow',
        9 : 'CAN message was lost',
        10 : 'Cannot be used by PDO',
        11 : 'Cannot write to flash memory',
        12 : 'Command not available in this unit mode',
        13 : 'Cannot reset communication - UART is busy',
        14 : 'Cannot perform CAN NMT service',
        15 : 'CAN Life time exceeded - motor shut down',
        16 : "The command attribute is array '[]' is expected",
        17 : 'Format of UL command is not valid - check the command definition',
        18 : 'Empty Assign',
        19 : 'Command syntax error',
        21 : 'Operand Out of Range',
        22 : 'Zero Division',
        23 : 'Command cannot be assigned',
        24 : 'Bad Operation',
        25 : 'Command Not Valid While Moving',
        26 : 'Profiler mode not supported in this unit mode (UM)',
        28 : 'Out Of Limit',
        29 : 'CAN set object return an abort when called from interpreter',
        30 : 'No program to continue',
        31 : 'CAN get object return an abort when called from interpreter',
        32 : 'Communication overrun, parity, noise, or framing error',
        33 : 'Bad sensor setting',
        34 : 'There is a conflict with another command',
        36 : 'Commutation method (CA[17]) or commutation table does not fit to sensor',
        37 : 'Two Or More Hall sensors are defined to the same place',
        38 : 'Motion start specified for the past',
        41 : 'Command is not supported by this product',
        42 : 'No Such Label',
        43 : 'CAN state machine in fault(object 0x6041 in DS-402)',
        45 : 'Return Error From Subroutine',
        46 : 'May Not Use Multi- capture Homing Mode With Stop Event',
        47 : 'Program does not exist or not Compiled',
        48 : 'Motor cold not start - fault reason in CD',
        50 : 'Stack Overflow',
        51 : 'Inhibit OR Abort inputs are active, Cannot start motor',
        52 : 'PVT Queue Full',
        53 : 'Only For Current',
        54 : 'Bad Data Base',
        55 : 'Bad Context',
        56 : 'The product grade does not support this command',
        57 : 'Motor Must be Off',
        58 : 'Motor Must be On',
        60 : 'Bad Unit Mode',
        61 : 'Data Base Reset',
        64 : 'Cannot set the index of an active table',
        65 : 'Disabled By SW',
        66 : 'Amplifier Not Ready',
        67 : 'Recorder Is Busy',
        68 : 'Required profiler mode is not supported',
        69 : 'Recorder Usage Error',
        70 : 'Recorder data Invalid',
        71 : 'Homing is busy',
        72 : 'Modulo range must be even',
        73 : 'Please Set Position',
        74 : 'Bad profile database, see 0x2081 for object number (EE[2])',
        77 : 'Buffer Too Large',
        78 : 'Out of Program Range',
        80 : 'ECAM data inconsistent',
        81 : 'Download failed see specific error in EE[3]',
        82 : 'Program Is Running',
        83 : 'Command is not permitted in a program.',
        84 : 'The System Is Not In Point To Point Mode',
        86 : 'PVT table is soon going to underflow',
        88 : 'ECAM last interval is larger than allowed',
        90 : 'CAN state machine not ready (object 0x6041 in DS-402)',
        91 : 'Bad PVT Head Pointer',
        92 : 'PDO not configured',
        93 : 'There is a wrong initiation value for this command',
        95 : 'ER[3] Too large for modulo setting applied',
        96 : 'User program time out',
        97 : 'RS232 receive buffer overflow',
        98 : 'Cannot measure current offsets',
        99 : 'Bad auxiliary sensor configuration',
        100 : 'The requested PWM value is not supported',
        101 : 'Absolute encoder setting problem',
        105 : 'Speed loop KP out of range',
        106 : 'Position loop KP out of range',
        110 : 'Too long number',
        111 : 'KV vector is invalid',
        112 : 'KV defines scheduled block but scheduling is off',
        113 : 'Exp task queue is full',
        114 : 'Exp task queue is empty',
        115 : 'Exp output queue is full',
        116 : 'Exp output queue is empty',
        117 : 'Bad KV setting for sensor filter',
        118 : 'Bad KV setting',
        119 : 'Analog Sensor filter out of range',
        120 : 'Analog Sensor filter may contain 0 or 2 blocks',
        121 : 'Please wait until Analog Sensor initialized',
        122 : 'Motion mode is not supported or with initialization conflict',
        123 : 'Profiler queue is full',
        125 : 'Personality not loaded',
        126 : 'User Program failed - variable out of program size',
        127 : 'Modulo range must be positive',
        128 : 'Bad variable index in database',
        129 : 'Variable is not an array',
        130 : 'Variable name does not exist',
        131 : 'Cannot record local variable',
        132 : 'Variable is an array',
        133 : 'Number of function input arguments is not as expected',
        134 : 'Cannot run local label/function with the XQ command',
        135 : 'Frequency identification failed',
        136 : 'Not a number',
        137 : 'Program already compiled',
        139 : 'The number of break points exceeds maximal number',
        140 : 'An attempt to set/clear break point at the not relevant line',
        141 : 'Boot Identity parameters section is not clear',
        142 : 'Checksum of data is not correct',
        143 : 'Missing boot identity parameters',
        144 : 'Numeric Stack underflow',
        145 : 'Numeric stack overflow',
        146 : 'Expression stack overflow',
        147 : 'Executable command within math expression',
        148 : 'Nothing in the expression',
        149 : 'Unexpected sentence termination',
        150 : 'Sentence terminator not found',
        151 : 'Parentheses mismatch',
        152 : 'Bad operand type',
        153 : 'Overflow in a numeric operator',
        154 : 'Address is out of data memory segment',
        155 : 'Beyond stack range',
        156 : 'Bad op-code',
        157 : 'No Available program stack',
        158 : 'Out of flash memory range',
        159 : 'Flash memory verification error',
        160 : 'Program aborted by another thread',
        161 : 'Program is not halted.',
        162 : 'Badly formatted number.',
        163 : 'There is not enough space in the program data segment. Try to reduce variable usage in the user program.',
        164 : 'EC command (not an error)',
        165 : 'An attempt was made to access serial flash memory while busy.',
        166 : 'Out of modulo range.',
        167 : 'Infinite loop in for loop - zero step',
        168 : 'Speed too large to start motor.',
        169 : 'Time out using peripheral.(overflo w or busy)',
        170 : 'Cannot erase sector in flash memory',
        171 : 'Cannot read from flash memory',
        172 : 'Cannot write to flash memory',
        173 : 'Executable area of program is too large',
        174 : 'Program has not been loaded',
        175 : 'Cannot write program checksum - clear program (CP)',
        176 : 'User code, variables and functions are too large',
        181 : 'Writing to Flash program area, failed',
        182 : 'PAL Burn Is In Process or no PAL is burned',
        183 : 'PAL Burn (PB Command) Is Disabled',
        184 : 'Capture option already used by other operation',
        185 : 'This element may be modified only when interpolation is not active',
        186 : 'Interpolation queue is full',
        187 : 'Incorrect Interpolation sub-mode',
        188 : 'Gantry slave is disabled',
        }
    
    def __init__(self,*args,**kwargs):
        Controller.__init__(self,*args,**kwargs)
        self._cnx = None
        
    def initialize(self):
        config = self.config.config_dict
        if get_comm_type(config) == UDP:
            opt = {'port':5001,'eol':';'}
        else:                   # SERIAL
            opt = {'baudrate':115200,'eol':';'}
            
        self._cnx = get_comm(config,**opt)
        self._elmostate = AxisState()
        for state,human in (('SLAVESWITCH','Slave switch activate'),
                            ('INHIBITSWITCH','Inhibit switch active'),
                            ('CLOSEDLOOPOPEN','Closed loop open'),
                            ('DRIVEFAULT','Problem into the drive')):
            self._elmostate.create_state(state,human)
            
    def initialize_hardware(self):
        # Check that the controller is alive
        try:
            self._query("VR\r",timeout=50e-3)
        except SocketTimeout:
            raise RuntimeError("Controller Elmo (%s) is not connected" % \
                               (self._cnx._host))

    def initialize_axis(self,axis):
        axis._mode = Cache(axis,"mode",default_value = None)
    
    def initialize_hardware_axis(self, axis):
        # Check user-mode
        mode= int(self._query("UM"))
        asked_mode = axis.config.get('user_mode',int,5)
        if (mode != asked_mode):
            self._query("UM=%d" % asked_mode)
        # Check closed loop on
        if self._query("MO") != '1':
            self.set_on(axis)
        mode= self._query("UM")
        axis._mode.value = int(mode)
        
    def set_on(self, axis):
        self._set_power(axis,True)

    def set_off(self, axis):
        self._set_power(axis,False)

    def _set_power(self, axis, activate):
        # Activate slave if needed
        if axis.config.get('control_slave',bool,False):
            self._query("OB[1]=%d" % activate)

        self._query("MO=%d" % activate)
        
    def _query(self,msg,in_error_code = False,**keys):
        send_message = msg + '\r'
        raw_reply = self._cnx.write_readline(send_message,**keys)
        if not raw_reply.startswith(send_message): # something weird happened
            self._cnx.close()
            raise RuntimeError("received reply: %s\n"
                               "expected message starts with %s" % msg)
        reply = raw_reply[len(send_message):]
        if not in_error_code and reply.endswith('?'):
            error_code = self._query("EC",in_error_code = True)
            try:
                error_code = int(error_code)
            except ValueError:  # Weird, don't know what to do
                pass
            else:
                human_error = self.ErrorCode.get(error_code,'Unknown')
                raise RuntimeError("Error %d (%s), Query (%s)" %
                                   (error_code,human_error,msg))
        return reply

    def start_jog(self,axis,velocity,direction):
        self._query("JV=%d" % velocity * direction)
        self._query("BG")

    def stop_jog(self,axis):
        self._query("JV=0")
        self._query("BG")
        #check if sync_hard needed

    def read_position(self,axis):
        if axis._mode == 2:
            return float(self._query("PX"))
        else:
            return float(self._query("DV[3]"))

    def set_position(self,axis,new_pos):
        pos = round(new_pos)
        self._set_power(axis,False)
        encodeur_name = "PY" if axis._mode == 4 else "PX"
        self._query("%s=%d" % (encodeur_name,pos))
        self._set_power(axis,True)
        self._query("PA=%d" % pos)
        return self.read_position(axis)
    
    def read_acceleration(self,axis):
        return int(self._query("AC"))

    def set_acceleration(self,axis,new_acc):
        self._query("AC=%d" % new_acc)
        self._query("DC=%d" % new_acc)
        return self.read_acceleration(axis)
    
    def read_velocity(self,axis):
        return float(self._query("SP"))

    def set_velocity(self,axis,new_vel):
        self._query("SP=%d" % new_vel)
        return self.read_velocity(axis)

    def home_search(self,axis,switch,set_pos = None):
        #can't set the position when searching home
        #we should change emotion to add an option in
        #home search i.e: set_pos = None
        if set_pos is not None:
            commands = ["HM[3]=3","HM[2]=%d" % round(set_pos),
                        "HM[4]=0","HM[5]=0","HM[1]=1"]
        else:
            commands = ["HM[3]=3","HM[4]=0","HM[5]=2","HM[1]=1"]
        for cmd in commands:
            self._query(cmd)
        #Start search
        step_sign = 1 if axis.config.get('steps_per_unit',float) > 0 else -1
        self._query("JV=%d", switch * step_sign * self.read_velocity())
        self._query("BG")
        
    def state(self,axis):
        state = self._elmostate.new()
        
        # check first that the controller is ready to move
        # bit0 of Status Register (page 3.135)
        ans = int(self._query("SR"))
        if(ans & (1<<7)):
            state.set("MOVING")
        if(ans & 0x1):          # problem into the drive
            state.set("DRIVEFAULT")
        if not (ans & (1<<4)):    # closed loop open
            state.set("CLOSEDLOOPOPEN")

        # Check limits
        ans = int(self._query("IP"))
        if axis.config.get('control_slave',bool,False):
            if ans & (1<<17):
                state.set("SLAVESWITCH")

        if ans & (1<<6):
            state.set("LIMPOS")
        if ans & (1<<7):
            state.set("LIMNEG")
        if ans & (1<<8):
            #should be checked in spec ends in MOT_EMERGENCY ?
            state.set("INHIBITSWITCH")

        # Check motion state
        # Wrong if homing
        # 
        ans = int(self._query("MS"))
        if ans == 0:
            state.set("READY")
        elif ans == 1 or ans == 2:
            state.set("MOVING")
        elif ans == 3:
            state.set("FAULT")

        return state
    
    def start_one(self,motion):
        # check first that the controller is ready to move
        # bit0 of Status Register (page 3.135)
        ans = int(self._query("SR"))
        if(ans & 0x1):
            raise RuntimeError("problem into the drive")
        if not (ans & (1<<4)):
            raise RuntimeError("closed loop open")

        self._query("PA=%d" % round(motion.target_pos))
        self._query("BG")

    def stop(self,axis):
        self._query("ST")
        #todo spec macros check if motor is in homing phase...

    @object_method(types_info=("None","int"))
    def get_user_mode(self,axis):
        return int(self._query("UM"))

    @object_method(types_info=("int","int"))
    def set_user_mode(self,axis,mode):
        commands = ["MO=0","UM=%d" % mode]
        if mode == 2:
            commands.append("PM=1")
        commands.append("MO=1")
        for cmd in commands:
            self._query(cmd)
        if mode == 5 or mode == 4:
            self.sync_hard()
        mode = int(self._query("UM"))
        axis._mode.value = mode
        return mode

    @object_method(types_info=("None",("float","float")))
    def jog_range(self,axis):
        #this method should be in emotion
        #todo move it has a generic
        return float(self._query("VL[2]")),float(self._query("VH[2]"))

    @object_method(types_info=("None","bool"))
    def get_enable_slave(self,axis):
        return bool(self._query("OB[1]"))

    @object_method(types_info=("bool","bool"))
    def set_enable_slave(self,axis,active):
        self._query("OB[1]=%d" % active)
        return bool(self._query("OB[1]"))
    
    #encoders
    def initialize_encoder(self,encoder):
        pass

    def read_encoder(self,encoder):
        return float(self._query("PX"))

    def set_encoder(self,encoder,steps):
        self._query("PX=%d" % steps)
Exemple #9
0
class Icepap(Controller):
    """
    IcePAP stepper controller without Deep Technology of Communication.
    But if you prefer to have it (DTC) move to IcePAP controller class.
    Use this class controller at your own risk, because you won't
    have any support...
    """
    STATUS_DISCODE = {
        0: ('POWERENA', 'power enabled'),
        1: ('NOTACTIVE', 'axis configured as not active'),
        2: ('ALARM', 'alarm condition'),
        3: ('REMRACKDIS', 'remote rack disable input signal'),
        4: ('LOCRACKDIS', 'local rack disable switch'),
        5: ('REMAXISDIS', 'remote axis disable input signal'),
        6: ('LOCAXISDIS', 'local axis disable switch'),
        7: ('SOFTDIS', 'software disable'),
    }

    STATUS_MODCODE = {
        0: ('OPER', 'operation mode'),
        1: ('PROG', 'programmation mode'),
        2: ('TEST', 'test mode'),
        3: ('FAIL', 'fail mode'),
    }
    STATUS_STOPCODE = {
        0: ('SCEOM', 'end of movement'),
        1: ('SCSTOP', 'last motion was stopped'),
        2: ('SCABORT', 'last motion was aborted'),
        3: ('SCLIMPOS', 'positive limitswitch reached'),
        4: ('SCLINNEG', 'negative limitswitch reached'),
        5: ('SCSETTLINGTO', 'settling timeout'),
        6: ('SCAXISDIS', 'axis disabled (no alarm)'),
        7: ('SCBIT7', 'n/a'),
        8: ('SCINTFAIL', 'internal failure'),
        9: ('SCMOTFAIL', 'motor failure'),
        10: ('SCPOWEROVL', 'power overload'),
        11: ('SCHEATOVL', 'driver overheating'),
        12: ('SCCLERROR', 'closed loop error'),
        13: ('SCCENCERROR', 'control encoder error'),
        14: ('SCBIT14', 'n/a'),
        15: ('SCEXTALARM', 'external alarm'),
    }

    def __init__(self, *args, **kwargs):
        Controller.__init__(self, *args, **kwargs)
        self._cnx = None
        self._last_axis_power_time = dict()

    def initialize(self):
        hostname = self.config.get("host")
        self._cnx = Command(hostname, 5000, eol='\n')

        self._icestate = AxisState()
        self._icestate.create_state("POWEROFF", "motor power is off")
        for codes in (self.STATUS_DISCODE, self.STATUS_MODCODE,
                      self.STATUS_STOPCODE):
            for state, desc in codes.values():
                self._icestate.create_state(state, desc)

    def finalize(self):
        if self._cnx is not None:
            self._cnx.close()

    def initialize_axis(self, axis):
        axis.address = axis.config.get("address", lambda x: x)

        if hasattr(axis, '_init_software'):
            axis._init_software()

    def initialize_hardware_axis(self, axis):
        if axis.config.get('autopower', converter=bool, default=True):
            try:
                self.set_on(axis)
            except:
                sys.excepthook(*sys.exc_info())

        if hasattr(axis, '_init_hardware'):
            axis._init_hardware()

    #Axis power management
    def set_on(self, axis):
        """
        Put the axis power on
        """
        self._power(axis, True)

    def set_off(self, axis):
        """
        Put the axis power off
        """
        self._power(axis, False)

    def _power(self, axis, power):
        _ackcommand(self._cnx,
                    "POWER %s %s" % ("ON" if power else "OFF", axis.address))
        self._last_axis_power_time[axis] = time.time()

    def read_position(self, axis, cache=True):
        pos_cmd = "FPOS" if cache else "POS"
        return int(_command(self._cnx, "?%s %s" % (pos_cmd, axis.address)))

    def set_position(self, axis, new_pos):
        if isinstance(axis, SlaveAxis):
            pre_cmd = "%d:DISPROT LINKED;" % axis.address
        else:
            pre_cmd = None
        _ackcommand(self._cnx,
                    "POS %s %d" % (axis.address, int(round(new_pos))),
                    pre_cmd=pre_cmd)
        return self.read_position(axis, cache=False)

    def read_velocity(self, axis):
        return float(_command(self._cnx, "?VELOCITY %s" % axis.address))

    def set_velocity(self, axis, new_velocity):
        _ackcommand(self._cnx, "VELOCITY %s %f" % (axis.address, new_velocity))
        return self.read_velocity(axis)

    def read_acceleration(self, axis):
        acctime = float(_command(self._cnx, "?ACCTIME %s" % axis.address))
        velocity = self.read_velocity(axis)
        return velocity / float(acctime)

    def set_acceleration(self, axis, new_acc):
        velocity = self.read_velocity(axis)
        new_acctime = velocity / new_acc

        _ackcommand(self._cnx, "ACCTIME %s %f" % (axis.address, new_acctime))
        return self.read_acceleration(axis)

    def state(self, axis):
        last_power_time = self._last_axis_power_time.get(axis, 0)
        if time.time() - last_power_time < 1.:
            status_cmd = "?STATUS"
        else:
            self._last_axis_power_time.pop(axis, None)
            status_cmd = "?FSTATUS"

        status = int(_command(self._cnx, "%s %s" % (status_cmd, axis.address)),
                     16)
        status ^= 1 << 23  #neg POWERON FLAG
        state = self._icestate.new()
        for mask, value in (((1 << 9), "READY"), ((1 << 10 | 1 << 11),
                                                  "MOVING"),
                            ((1 << 18), "LIMPOS"), ((1 << 19), "LIMNEG"),
                            ((1 << 20), "HOME"), ((1 << 23), "POWEROFF")):
            if status & mask:
                state.set(value)

        state_mode = (status >> 2) & 0x3
        if state_mode:
            state.set(self.STATUS_MODCODE.get(state_mode)[0])

        stop_code = (status >> 14) & 0xf
        if stop_code:
            state.set(self.STATUS_STOPCODE.get(stop_code)[0])

        disable_condition = (status >> 4) & 0x7
        if disable_condition:
            state.set(self.STATUS_DISCODE.get(disable_condition)[0])

        if state.READY:
            #if motor is ready then no need to investigate deeper
            return state

        if not state.MOVING:
            # it seems it is not safe to call warning and/or alarm commands
            # while homing motor, so let's not ask if motor is moving
            if status & (1 << 13):
                try:
                    warning = _command(self._cnx, "%d:?WARNING" % axis.address)
                except TypeError:
                    pass
                else:
                    warn_str = "warning condition: \n" + warning
                    status.create_state("WARNING", warn_str)
                    status.set("WARNING")

            try:
                alarm = _command(self._cnx, "%d:?ALARM" % axis.address)
            except (RuntimeError, TypeError):
                pass
            else:
                if alarm != "NO":
                    alarm_dsc = "alarm condition: " + str(alarm)
                    state.create_state("ALARMDESC", alarm_dsc)
                    state.set("ALARMDESC")

        return state

    def get_info(self, axis):
        pre_cmd = '%s:' % axis.address
        r = "MOTOR   : %s\n" % axis.name
        r += "SYSTEM  : %s (ID: %s) (VER: %s)\n" % (
            self._cnx._host, _command(self._cnx,
                                      "0:?ID"), _command(self._cnx, "?VER"))
        r += "DRIVER  : %s\n" % axis.address
        r += "POWER   : %s\n" % _command(self._cnx, pre_cmd + "?POWER")
        r += "CLOOP   : %s\n" % _command(self._cnx, pre_cmd + "?PCLOOP")
        r += "WARNING : %s\n" % _command(self._cnx, pre_cmd + "?WARNING")
        r += "ALARM   : %s\n" % _command(self._cnx, pre_cmd + "?ALARM")
        return r

    def raw_write(self, message, data=None):
        return _command(self._cnx, message, data)

    def raw_write_read(self, message, data=None):
        return _ackcommand(self._cnx, message, data)

    def prepare_move(self, motion):
        pass

    def start_one(self, motion):
        if isinstance(motion.axis, SlaveAxis):
            pre_cmd = "%d:DISPROT LINKED;" % motion.axis.address
        else:
            pre_cmd = None

        _ackcommand(self._cnx,
                    "MOVE %s %d" % (motion.axis.address, motion.target_pos),
                    pre_cmd=pre_cmd)

    def start_all(self, *motions):
        if motions > 1:
            cmd = "MOVE GROUP "
            cmd += ' '.join(
                ["%s %d" % (m.axis.address, m.target_pos) for m in motions])
            _ackcommand(self._cnx, cmd)
        elif motions:
            self.start_one(motions[0])

    def stop(self, axis):
        _command(self._cnx, "STOP %s" % axis.address)

    def stop_all(self, *motions):
        for motion in motions:
            self.stop(motion.axis)

    def home_search(self, axis, switch):
        cmd = "HOME " + ("+1" if switch > 0 else "-1")
        _ackcommand(self._cnx, "%s:%s" % (axis.address, cmd))
        # IcePAP status is not immediately MOVING after home search command is sent
        gevent.sleep(0.2)

    def home_state(self, axis):
        s = self.state(axis)
        if s != 'READY' and s != 'POWEROFF':
            s.set('MOVING')
        return s

    def limit_search(self, axis, limit):
        cmd = "SRCH LIM" + ("+" if limit > 0 else "-")
        _ackcommand(self._cnx, "%s:%s" % (axis.address, cmd))
        # TODO: MG18Nov14: remove this sleep (state is not immediately MOVING)
        gevent.sleep(0.1)

    def initialize_encoder(self, encoder):
        # Get axis config from bliss config
        # address form is XY : X=rack {0..?} Y=driver {1..8}
        encoder.address = encoder.config.get("address", int)

        # Get optional encoder input to read
        enctype = encoder.config.get("type", str, "ENCIN").upper()
        # Minium check on encoder input
        if enctype not in [
                'ENCIN', 'ABSENC', 'INPOS', 'MOTOR', 'AXIS', 'SYNC'
        ]:
            raise ValueError('Invalid encoder type')
        encoder.enctype = enctype

    def read_encoder(self, encoder):
        value = _command(self._cnx,
                         "?ENC %s %d" % (encoder.enctype, encoder.address))
        return int(value)

    def set_encoder(self, encoder, steps):
        _ackcommand(self._cnx,
                    "ENC %s %d %d" % (encoder.enctype, encoder.address, steps))

    def set_event_positions(self, axis_or_encoder, positions):
        int_position = numpy.array(positions, dtype=numpy.int32)
        #position has to be ordered
        int_position.sort()
        address = axis_or_encoder.address
        if not len(int_position):
            _ackcommand(self._cnx, "%s:ECAMDAT CLEAR" % address)
            return

        if isinstance(axis_or_encoder, Axis):
            source = 'AXIS'
        else:  # encoder
            source = 'MEASURE'

        #load trigger positions
        _ackcommand(self._cnx, "%s:*ECAMDAT %s DWORD" % (address, source),
                    int_position)
        # send the trigger on the multiplexer
        _ackcommand(self._cnx, "%s:SYNCAUX eCAM" % address)

    def get_event_positions(self, axis_or_encoder):
        """
        For this controller this method should be use
        for debugging purposed only... 
        """
        address = axis_or_encoder.address
        #Get the number of positions
        reply = _command(self._cnx, "%d:?ECAMDAT" % address)
        reply_exp = re.compile("(\w+) +([+-]?\d+) +([+-]?\d+) +(\d+)")
        m = reply_exp.match(reply)
        if m is None:
            raise RuntimeError("Reply Didn't expected: %s" % reply)
        source = m.group(1)
        nb = int(m.group(4))

        if isinstance(axis_or_encoder, Axis):
            nb = nb if source == 'AXIS' else 0
        else:  # encoder
            nb = nb if source == "MEASURE" else 0

        positions = numpy.zeros((nb, ), dtype=numpy.int32)
        if nb > 0:
            reply_exp = re.compile(".+: +([+-]?\d+)")
            reply = _command(self._cnx, "%d:?ECAMDAT %d" % (address, nb))
            for i, line in enumerate(reply.split('\n')):
                m = reply_exp.match(line)
                if m:
                    pos = int(m.group(1))
                    positions[i] = pos
        return positions

    def get_linked_axis(self):
        reply = _command(self._cnx, "?LINKED")
        linked = dict()
        for line in reply.strip().split('\n'):
            values = line.split()
            linked[values[0]] = [int(x) for x in values[1:]]
        return linked

    @object_method(types_info=("bool", "bool"))
    def activate_closed_loop(self, axis, active):
        _command(self._cnx,
                 "#%s:PCLOOP %s" % (axis.address, "ON" if active else "OFF"))
        return active

    @object_method(types_info=("None", "bool"))
    def is_closed_loop_activate(self, axis):
        return True if _command(self._cnx, "%s:?PCLOOP" %
                                axis.address) == 'ON' else False

    @object_method(types_info=("None", "None"))
    def reset_closed_loop(self, axis):
        measure_position = int(
            _command(self._cnx, "%s:?POS MEASURE" % axis.address))
        self.set_position(axis, measure_position)
        if axis.config.get('autopower', converter=bool, default=True):
            self.set_on(axis)
        axis.sync_hard()

    @object_method(types_info=("None", "int"))
    def temperature(self, axis):
        return int(_command(self._cnx, "%s:?MEAS T" % axis.address))

    @object_method(types_info=(("float", "bool"), "None"))
    def set_tracking_positions(self, axis, positions, cyclic=False):
        """
        Send position to the controller which will be tracked.

        positions --  are expressed in user unit
        cyclic -- cyclic position or not default False

        @see activate_track method
        """
        address = axis.address
        if not len(positions):
            _ackcommand(self._cnx, "%s:LISTDAT CLEAR" % address)
            return

        dial_positions = axis.user2dial(
            numpy.array(positions, dtype=numpy.float))
        step_positions = numpy.array(dial_positions * axis.steps_per_unit,
                                     dtype=numpy.int32)
        _ackcommand(
            self._cnx, "%d:*LISTDAT %s DWORD" %
            (address, "CYCLIC" if cyclic else "NOCYCLIC"), step_positions)

    @object_method(types_info=("None", ("float", "bool")))
    def get_tracking_positions(self, axis):
        """
        Get the tacking positions.
        This method should only be use for debugging
        return a tuple with (positions,cyclic flag)
        """
        address = axis.address
        #Get the number of positions
        reply = _command(self._cnx, "%d:?LISTDAT" % address)
        reply_exp = re.compile("(\d+) *(\w+)?")
        m = reply_exp.match(reply)
        if m is None:
            raise RuntimeError("Reply didn't expected: %s" % reply)
        nb = int(m.group(1))
        positions = numpy.zeros((nb, ), dtype=numpy.int32)
        cyclic = True if m.group(2) == "CYCLIC" else False
        if nb > 0:
            reply_exp = re.compile(".+: +([+-]?\d+)")
            reply = _command(self._cnx, "%d:?LISTDAT %d" % (address, nb))
            for i, line in enumerate(reply.split('\n')):
                m = reply_exp.match(line)
                if m:
                    pos = int(m.group(1))
                    positions[i] = pos
            dial_positions = positions / axis.steps_per_unit
            positions = axis.dial2user(dial_positions)
        return positions, cyclic

    @object_method(types_info=(("bool", "str"), "None"))
    def activate_tracking(self, axis, activate, mode=None):
        """
        Activate/Deactivate the tracking position depending on
        activate flag
        mode -- default "INPOS" if None.
        mode can be :
           - SYNC   -> Internal SYNC signal
           - ENCIN  -> ENCIN signal
           - INPOS  -> INPOS signal
           - ABSENC -> ABSENC signal
        """
        address = axis.address

        if not activate:
            _ackcommand(self._cnx, "STOP %d" % address)
            axis.sync_hard()
        else:
            if mode is None: mode = "INPOS"
            possibles_modes = ["SYNC", "ENCIN", "INPOS", "ABSENC"]
            if mode not in possibles_modes:
                raise ValueError("mode %s is not managed, can only choose %s" %
                                 (mode, possibles_modes))
            if mode == "INPOS":
                _ackcommand(self._cnx, "%d:POS INPOS 0" % address)
            _ackcommand(self._cnx, "%d:LTRACK %s" % (address, mode))

    @object_method(types_info=("float", "None"))
    def blink(self, axis, second=3.):
        """
        Blink axis driver
        """
        _command(self._cnx, "%d:BLINK %f" % (axis.address, second))

    def reset(self):
        _command(self._cnx, "RESET")

    def mdspreset(self):
        """
        Reset the MASTER DSP
        """
        _command(self._cnx, "_dsprst")

    def reboot(self):
        _command(self._cnx, "REBOOT")
        self._cnx.close()
Exemple #10
0
class Mockup(Controller):
    def __init__(self, name, config, axes, encoders):
        Controller.__init__(self, name, config, axes, encoders)

        self._axis_moves = {}
        self.__encoders = {}

        self.__error_mode = False
        self._hw_status = AxisState("READY")
        self.__hw_limit = (None, None)

        self._hw_status.create_state("PARKED", "mot au parking")

        # Access to the config.
        try:
            self.host = self.config.get("host")
        except:
            elog.debug("no 'host' defined in config for %s" % name)

        # Adds Mockup-specific settings.
        self.axis_settings.add('init_count', int)
        self.axis_settings.add('atrubi', float)
        self.axis_settings.add('round_earth', bool)
        self.axis_settings.add('geocentrisme', bool)

    """
    Controller initialization actions.
    """
    def initialize(self):
        # hardware initialization
        for axis_name, axis in self.axes.iteritems():
            axis.settings.set('init_count', 0)
            axis.settings.set('atrubi', 777)
            axis.settings.set('round_earth', True)
            axis.settings.set('geocentrisme', False)

            axis.__vel = None
            axis.__acc = None

    """
    Axes initialization actions.
    """
    def initialize_axis(self, axis):
        def set_pos(move_done, axis=axis):
            if move_done:
                self.set_position(axis, axis.dial()*axis.steps_per_unit)

        self._axis_moves[axis] = {
            "measured_simul": False,
            "measured_noise": 0.0,
            "end_t": 0,
            "end_pos": 0,
            "move_done_cb": set_pos }

        event.connect(axis, "move_done", set_pos)

        # this is to test axis are initialized only once
        axis.settings.set('init_count', axis.settings.get('init_count') + 1)

        # Add new axis oject methods as tango commands.
        add_axis_method(axis, self.custom_get_twice, types_info=("int", "int"))
        add_axis_method(axis, self.custom_get_chapi, types_info=("str", "str"))
        add_axis_method(axis, self.custom_send_command, types_info=("str", "None"))
        add_axis_method(axis, self.custom_command_no_types, types_info=("None", "None"))
        add_axis_method(axis, self.custom_set_measured_noise, types_info=("float", "None"))
        add_axis_method(axis, self._set_closed_loop, name = "Set_Closed_Loop", types_info = (bool, None))

        add_axis_method(axis, self.put_discrepancy, types_info=("int", "None"))


        if axis.encoder:
            self.__encoders.setdefault(axis.encoder, {})["axis"] = axis


    def initialize_encoder(self, encoder):
        self.__encoders.setdefault(encoder, {})["measured_noise"] = 0.0
        self.__encoders[encoder]["steps"] = None

    """
    Actions to perform at controller closing.
    """
    def finalize(self):
        pass

    def set_hw_limit(self, axis, low_limit, high_limit):
        if low_limit is not None:
            ll= axis.user2dial(low_limit)*axis.steps_per_unit
        else:
            ll = None
        if high_limit is not None:
            hl = axis.user2dial(high_limit)*axis.steps_per_unit
        else:
            hl = None
        if hl is not None and hl < ll:
          self.__hw_limit = (hl, ll)
        else:
          self.__hw_limit = (ll, hl)

    def start_all(self, *motion_list):
        if self.__error_mode:
            raise RuntimeError("Cannot start because error mode is set")
        t0 = time.time()
        for motion in motion_list:
            self.start_one(motion, t0=t0)

    def start_one(self, motion, t0=None):
        if self.__error_mode:
            raise RuntimeError("Cannot start because error mode is set")
        axis = motion.axis
        t0 = t0 or time.time()
        pos = self.read_position(axis)
        v = self.read_velocity(axis)
        ll, hl = self.__hw_limit
        if hl is not None and motion.target_pos > hl:
            d=hl-motion.target_pos
            motion.delta -= d
            motion.target_pos = hl
        if ll is not None and motion.target_pos < ll:
            d=ll-motion.target_pos
            motion.delta -= d
            motion.target_pos = ll
        self._axis_moves[axis].update({
            "start_pos": pos,
            "delta": motion.delta,
            "end_pos": motion.target_pos,
            "end_t": t0 +
            math.fabs(
                motion.delta) /
            float(v),
            "t0": t0})

    def read_position(self, axis):
        """
        Returns the position (measured or desired) taken from controller
        in controller unit (steps).
        """

        # handle read out during a motion
        if self._axis_moves[axis]["end_t"]:
            # motor is moving
            t = time.time()
            v = self.read_velocity(axis)
            d = math.copysign(1, self._axis_moves[axis]["delta"])
            dt = t - self._axis_moves[axis]["t0"]  # t0=time at start_one.
            pos = self._axis_moves[axis]["start_pos"] + d * dt * v
        else:
            pos = self._axis_moves[axis]["end_pos"]

        return int(round(pos))

    def read_encoder(self, encoder):
        """
        returns encoder position.
        unit : 'encoder steps'
        """

        axis = self.__encoders[encoder]["axis"]

        if self.__encoders[encoder]["measured_noise"] != 0.0:
            # Simulates noisy encoder.
            amplitude = self.__encoders[encoder]["measured_noise"]
            noise_mm = random.uniform(-amplitude, amplitude)

            _pos = self.read_position(axis) / axis.steps_per_unit
            _pos += noise_mm

            self.__encoders[encoder]["steps"] = _pos * encoder.steps_per_unit

        else:
            # "Perfect encoder"
            if self.__encoders[encoder]["steps"] is None:
                self.__encoders[encoder]["steps"] = self.read_position(axis) / axis.steps_per_unit

        return self.__encoders[encoder]["steps"]

    def set_encoder(self, encoder, encoder_steps):
        self.__encoders[encoder]["steps"]=encoder_steps


    """
    VELOCITY
    """
    def read_velocity(self, axis):
        """
        Returns the current velocity taken from controller
        in motor units.
        """
        return axis.__vel

    def set_velocity(self, axis, new_velocity):
        """
        <new_velocity> is in motor units
        """
        axis.__vel = new_velocity

    """
    ACCELERATION
    """
    def read_acceleration(self, axis):
        """
        must return acceleration in controller units / s2
        """
        return axis.__acc

    def set_acceleration(self, axis, new_acceleration):
        """
        <new_acceleration> is in controller units / s2
        """
        axis.__acc = new_acceleration

    """
    ON / OFF
    """
    def set_on(self, axis):
        self._hw_status = "READY"

    def set_off(self, axis):
        self._hw_status = "OFF"

    """
    Hard limits
    """
    def _check_hw_limits(self, axis):
        ll, hl = self.__hw_limit
        pos = self.read_position(axis)
        if ll is not None and pos <= ll:
            return AxisState("READY", "LIMNEG")
        elif hl is not None and pos >= hl:
            return AxisState("READY", "LIMPOS")
        return AxisState("READY")

    """
    STATE
    """
    def state(self, axis):
        if self._hw_status == "PARKED":
            return AxisState("PARKED")

        if self._hw_status == "OFF":
            return AxisState("OFF")

        if self._axis_moves[axis]["end_t"] > time.time():
           return AxisState("MOVING")
        else:
           self._axis_moves[axis]["end_t"]=0
           return self._check_hw_limits(axis)

    """
    Must send a command to the controller to abort the motion of given axis.
    """
    def stop(self, axis):
        self._axis_moves[axis]["end_pos"] = self.read_position(axis)
        self._axis_moves[axis]["end_t"] = 0

    def stop_all(self, *motion_list):
        for motion in motion_list:
            axis = motion.axis
            self._axis_moves[axis]["end_pos"] = self.read_position(axis)
            self._axis_moves[axis]["end_t"] = 0

    """
    HOME and limits search
    """
    def home_search(self, axis, switch):
        self._axis_moves[axis]["start_pos"] = self._axis_moves[axis]["end_pos"]
        self._axis_moves[axis]["end_pos"] = 0
        self._axis_moves[axis]["delta"] = 0
        self._axis_moves[axis]["end_t"] = 0
        self._axis_moves[axis]["t0"] = time.time()
        self._axis_moves[axis]["home_search_start_time"] = time.time()

#    def home_set_hardware_position(self, axis, home_pos):
#        raise NotImplementedError

    def home_state(self, axis):
        if(time.time() - self._axis_moves[axis]["home_search_start_time"]) > 2:
            return AxisState("READY")
        else:
            return AxisState("MOVING")

    def limit_search(self, axis, limit):
        self._axis_moves[axis]["start_pos"] = self._axis_moves[axis]["end_pos"]
        self._axis_moves[axis]["end_pos"] = 1E6 if limit > 0 else -1E6
        self._axis_moves[axis]["delta"] = self._axis_moves[axis]["end_pos"] #this is just for direction sign
        self._axis_moves[axis]["end_pos"] *= axis.steps_per_unit
        self._axis_moves[axis]["end_t"] = time.time() + 2
        self._axis_moves[axis]["t0"] = time.time()

    def get_info(self, axis):
        return "turlututu chapo pointu : %s" % (axis.name)

    def raw_write(self, axis, com):
        print ("raw_write:  com = %s" % com)

    def raw_write_read(self, axis, com):
        return com + ">-<" + com

    def set_position(self, axis, pos):
        self._axis_moves[axis]["end_pos"] = pos
        self._axis_moves[axis]["end_t"] = 0
        return pos

    def put_discrepancy(self, axis, disc):
        self._axis_moves[axis]["end_pos"] += disc

    """
    Custom axis methods
    """
    # VOID VOID
    @axis_method
    def custom_park(self, axis):
        print "parking"
        self._hw_status.clear()
        self._hw_status.set("PARKED")

    # VOID LONG
    @axis_method(types_info=("None", "int"))
    def custom_get_forty_two(self, axis):
        return 42

    # LONG LONG
    def custom_get_twice(self, axis, LongValue):
        return LongValue * 2

    # STRING STRING
    def custom_get_chapi(self, axis, value):
        if value == "chapi":
            return "chapo"
        elif value == "titi":
            return "toto"
        else:
            return "bla"

    # STRING VOID
    def custom_send_command(self, axis, value):
        print "command=", value

    # BOOL NONE
    def _set_closed_loop(self, axis, onoff = True):
        print "I set the closed loop ", onoff

    # Types by default
    def custom_command_no_types(self, axis):
        print "print with no types"

    def custom_set_measured_noise(self, axis, noise):
        """
        Custom axis method to add a random noise, given in user units,
        to measured positions. Set noise value to 0 to have a measured
        position equal to target position.
        By the way we add a ref to the coresponding axis.
        """
        self.__encoders[axis.encoder]["measured_noise"] = noise
        self.__encoders[axis.encoder]["axis"] = axis

    def set_error(self, error_mode):
        self.__error_mode = error_mode
Exemple #11
0
def test_bad_name():
    s = AxisState()
    with pytest.raises(ValueError):
        s.create_state("A bad state")
Exemple #12
0
class Mockup(Controller):
    def __init__(self, *args, **kwargs):
        Controller.__init__(self, *args, **kwargs)

        self._axis_moves = {}
        self.__encoders = {}

        # Custom attributes.
        self.__voltages = {}
        self.__cust_attr_float = {}

        self.__error_mode = False
        self._hw_state = AxisState("READY")
        self.__hw_limit = (None, None)

        self._hw_state.create_state("PARKED", "mot au parking")

        # Access to the config.
        try:
            self.host = self.config.get("host")
        except:
            elog.debug("no 'host' defined in config for %s" % self.name)

        # Adds Mockup-specific settings.
        self.axis_settings.add('init_count', int)
        self.axis_settings.add('hw_position', float)

    """
    Controller initialization actions.
    """
    def initialize(self):
        # hardware initialization
        for axis_name, axis in self.axes.iteritems():
            axis.settings.set('init_count', 0)

    """
    Axes initialization actions.
    """
    def initialize_axis(self, axis):
        # this is to protect position reading,
        # indeed the mockup controller uses redis to store
        # a 'hardware position', and it is not allowed
        # to read a position before it has been written
        def set_pos(move_done, axis=axis):
            if move_done:
                self.set_position(axis, axis.dial()*axis.steps_per_unit)

        self._axis_moves[axis] = {
            "end_t": None,
            "move_done_cb": set_pos }

        if axis.settings.get('hw_position') is None:
            axis.settings.set('hw_position', 0)
        self._axis_moves[axis]['start_pos'] = self.read_position(axis)
        self._axis_moves[axis]['target'] = self._axis_moves[axis]['start_pos']

        event.connect(axis, "move_done", set_pos)

        self.__voltages[axis] = axis.config.get("default_voltage", 
                                                int, default=220)
        self.__cust_attr_float[axis] = axis.config.get("default_cust_attr",
                                                       float, default=3.14)

        # this is to test axis are initialized only once
        axis.settings.set('init_count', axis.settings.get('init_count') + 1)

        if axis.encoder:
            self.__encoders.setdefault(axis.encoder, {})["axis"] = axis

    def initialize_encoder(self, encoder):
        self.__encoders.setdefault(encoder, {})["measured_noise"] = None
        self.__encoders[encoder]["steps"] = None

    """
    Actions to perform at controller closing.
    """
    def finalize(self):
        pass

    def set_hw_limits(self, axis, low_limit, high_limit):
        if low_limit is not None:
            ll= axis.user2dial(low_limit)*axis.steps_per_unit
        else:
            ll = None
        if high_limit is not None:
            hl = axis.user2dial(high_limit)*axis.steps_per_unit
        else:
            hl = None
        if hl is not None and hl < ll:
          self.__hw_limit = (hl, ll)
        else:
          self.__hw_limit = (ll, hl)

    def start_all(self, *motion_list):
        if self.__error_mode:
            raise RuntimeError("Cannot start because error mode is set")
        t0 = time.time()
        for motion in motion_list:
            self.start_one(motion, t0=t0)

    def start_one(self, motion, t0=None):
        if self.__error_mode:
            raise RuntimeError("Cannot start because error mode is set")
        axis = motion.axis
        t0 = t0 or time.time()
        pos = self.read_position(axis)
        v = self.read_velocity(axis)
        ll, hl = self.__hw_limit
        end_pos = motion.target_pos
        if hl is not None and end_pos > hl:
            end_pos = hl
        if ll is not None and end_pos < ll:
            end_pos = ll
        delta = motion.delta + end_pos - motion.target_pos
        self._axis_moves[axis].update({
            "start_pos": pos,
            "delta": delta,
            "end_t": t0 + math.fabs(delta) / float(v),
            "target": end_pos,
            "t0": t0})

    def start_jog(self, axis, velocity, direction):
        t0 = time.time() 
        pos = self.read_position(axis)
        self.set_velocity(axis, velocity)
        self._axis_moves[axis].update({ 
            "start_pos": pos,
            "delta": direction,
            "end_t": t0+1E9,
            "t0": t0})

    def read_position(self, axis, t=None):
        """
        Returns the position (measured or desired) taken from controller
        in controller unit (steps).
        """
        t = t or time.time()
        # handle read out during a motion
        end_t = self._axis_moves[axis]["end_t"]
        if end_t is not None and t >= end_t:
            pos = self._axis_moves[axis]["target"]
            axis.settings.set('hw_position', pos)
        elif end_t:
            # motor is moving
            v = self.read_velocity(axis)
            a = self.read_acceleration(axis)
            d = math.copysign(1, self._axis_moves[axis]["delta"])
            dt = t - self._axis_moves[axis]["t0"]  # t0=time at start_one.
            acctime = min(dt, v/a)
            dt -= acctime
            pos = self._axis_moves[axis]["start_pos"] + d*a*0.5*acctime**2 
            if dt > 0:
                pos += d * dt * v
            axis.settings.set('hw_position', pos)
        else:
            pos = axis.settings.get('hw_position')

        return int(round(pos))

    def read_encoder(self, encoder):
        """
        returns encoder position.
        unit : 'encoder steps'
        """
        if self.__encoders[encoder]["steps"] is not None:
            enc_steps = self.__encoders[encoder]["steps"]
        else:
            axis = self.__encoders[encoder]["axis"]

            _pos = self.read_position(axis) / float(axis.steps_per_unit)

            if self.__encoders[encoder]["measured_noise"] > 0:
                # Simulates noisy encoder.
                amplitude = self.__encoders[encoder]["measured_noise"]
                noise_mm = random.uniform(-amplitude, amplitude)

                _pos += noise_mm

                enc_steps = _pos * encoder.steps_per_unit
            else:
                # "Perfect" encoder
                enc_steps = _pos * encoder.steps_per_unit

        self.__encoders[encoder]["steps"] = None

        return enc_steps

    def set_encoder(self, encoder, encoder_steps):
        self.__encoders[encoder]["steps"] = encoder_steps

    """
    VELOCITY
    """
    def read_velocity(self, axis):
        """
        Returns the current velocity taken from controller
        in motor units.
        """
        return axis.settings.get('velocity')*abs(axis.steps_per_unit)

    def set_velocity(self, axis, new_velocity):
        """
        <new_velocity> is in motor units
        """
        vel = new_velocity/abs(axis.steps_per_unit)
        axis.settings.set('velocity', vel)
        return vel

    """
    ACCELERATION
    """
    def read_acceleration(self, axis):
        """
        must return acceleration in controller units / s2
        """
        return axis.settings.get('acceleration')*abs(axis.steps_per_unit)

    def set_acceleration(self, axis, new_acceleration):
        """
        <new_acceleration> is in controller units / s2
        """
        acc = new_acceleration/abs(axis.steps_per_unit)
        axis.settings.set('acceleration', acc) 
        return acc

    """
    ON / OFF
    """
    def set_on(self, axis):
        self._hw_state.clear()
        self._hw_state.set("READY")

    def set_off(self, axis):
        self._hw_state.set("OFF")

    """
    Hard limits
    """
    def _check_hw_limits(self, axis):
        ll, hl = self.__hw_limit
        pos = self.read_position(axis)
        if ll is not None and pos <= ll:
            return AxisState("READY", "LIMNEG")
        elif hl is not None and pos >= hl:
            return AxisState("READY", "LIMPOS")
        if self._hw_state == "OFF":
            return AxisState("OFF")
        else:
            s = AxisState(self._hw_state)
            s.set("READY")
            return s

    """
    STATE
    """
    def state(self, axis):
        if self._axis_moves[axis]["end_t"] > time.time():
           return AxisState("MOVING")
        else:
           self.read_position(axis, t=self._axis_moves[axis]["end_t"])
           self._axis_moves[axis]["end_t"] = None
           self._axis_moves[axis]["delta"] = 0
           return self._check_hw_limits(axis)

    """
    Must send a command to the controller to abort the motion of given axis.
    """
    def stop(self, axis, t=None):
        if self._axis_moves[axis]["end_t"]:
            self._axis_moves[axis]["target"] = self.read_position(axis, t=t)
            self._axis_moves[axis]["end_t"] = None

    def stop_all(self, *motion_list):
        t = time.time()
        for motion in motion_list:
            self.stop(motion.axis, t=t)

    """
    HOME and limits search
    """
    def home_search(self, axis, switch):
        self._axis_moves[axis]["delta"] = switch
        self._axis_moves[axis]["end_t"] = None
        self._axis_moves[axis]["t0"] = time.time()
        self._axis_moves[axis]["home_search_start_time"] = time.time()

#    def home_set_hardware_position(self, axis, home_pos):
#        raise NotImplementedError

    def home_state(self, axis):
        if(time.time() - self._axis_moves[axis]["home_search_start_time"]) > 1:
            axis.settings.set("hw_position", 0)
            return AxisState("READY")
        else:
            return AxisState("MOVING")

    def limit_search(self, axis, limit):
        self._axis_moves[axis]["target"] = 1e6*limit*axis.steps_per_unit
        self._axis_moves[axis]["delta"] = limit
        self._axis_moves[axis]["end_t"] = time.time() + 1
        self._axis_moves[axis]["t0"] = time.time()

    def get_info(self, axis):
        return "turlututu chapo pointu : %s" % (axis.name)

    def get_id(self, axis):
        return "MOCKUP AXIS %s" % (axis.name)

    def set_position(self, axis, pos):
        if self._axis_moves[axis]["end_t"]:
            raise RuntimeError("Cannot set position while moving !")
        
        axis.settings.set('hw_position', pos)
        self._axis_moves[axis]['target'] = pos
        self._axis_moves[axis]["end_t"] = None

        return pos

    def put_discrepancy(self, axis, disc):
        self.set_position(axis, self.read_position(axis)+disc)

    """
    Custom axis methods
    """
    # VOID VOID
    @object_method
    def custom_park(self, axis):
        elog.debug("custom_park : parking")
        self._hw_state.set("PARKED")

    # VOID LONG
    @object_method(types_info=("None", "int"))
    def custom_get_forty_two(self, axis):
        return 42

    # LONG LONG  + renaming.
    @object_method(name= "CustomGetTwice", types_info=("int", "int"))
    def custom_get_twice(self, axis, LongValue):
        return LongValue * 2

    # STRING STRING
    @object_method(types_info=("str", "str"))
    def custom_get_chapi(self, axis, value):
        if value == "chapi":
            return "chapo"
        elif value == "titi":
            return "toto"
        else:
            return "bla"

    # STRING VOID
    @object_method(types_info=("str", "None"))
    def custom_send_command(self, axis, value):
        elog.debug("custom_send_command(axis=%s value=%r):" % (axis.name, value))

    # BOOL NONE
    @object_method(name="Set_Closed_Loop", types_info=("bool", "None"))
    def _set_closed_loop(self, axis, onoff = True):
        pass #print "I set the closed loop ", onoff

    # Types by default (None, None)
    @object_method
    def custom_command_no_types(self, axis):
        print "print with no types"

    @object_method
    def generate_error(self, axis):
        # For testing purposes.
        raise RuntimeError("Testing Error")

    def custom_get_measured_noise(self, axis):
        noise = 0.0
        if not axis.encoder in self.__encoders:
            raise KeyError("cannot read measured noise: %s "
                           "doesn't have encoder" % axis.name)
        noise = self.__encoders[axis.encoder].get("measured_noise", None)

    @object_method(types_info=("float", "None"))
    def custom_set_measured_noise(self, axis, noise):
        """
        Custom axis method to add a random noise, given in user units,
        to measured positions. Set noise value to 0 to have a measured
        position equal to target position.
        By the way we add a ref to the coresponding axis.
        """
        self.__encoders[axis.encoder]["measured_noise"] = noise
        self.__encoders[axis.encoder]["axis"] = axis

    def set_error(self, error_mode):
        self.__error_mode = error_mode

    """
    Custom attributes methods
    """

    @object_attribute_get(type_info="int")
    def get_voltage(self, axis):
        return self.__voltages.setdefault(axis, 10000)

    @object_attribute_set(type_info="int")
    def set_voltage(self, axis, voltage):
        self.__voltages[axis] = voltage

    @object_attribute_get(type_info="float")
    def get_cust_attr_float(self, axis):
        return self.__cust_attr_float.setdefault(axis, 9.999)

    @object_attribute_set(type_info="float")
    def set_cust_attr_float(self, axis, value):
        self.__cust_attr_float[axis] = value