コード例 #1
0
    def load_additional_args(self, config):
        """
        """
        args = [('Motion', 'steps', 'int'), ('Motion', 'min_steps', 'int'),
                ('Motion', 'sign'), ('Motion', 'velocity'),
                ('Motion', 'acceleration'), ('Homing', 'home_delay'),
                ('Homing', 'home_velocity'), ('Homing', 'home_acceleration'),
                ('Homing', 'home_at_startup', 'boolean'), ('General', 'min'),
                ('General', 'max'), ('General', 'nominal_position'),
                ('General', 'units')]
        for args in args:
            if len(args) == 3:
                section, key, cast = args
            else:
                cast = 'float'
                section, key = args

            self.set_attribute(config, key, section, key, cast=cast)

        if config.has_option('Motion', 'hysteresis'):
            '''
                if hysteresis is <0
                correction is done when moving in the negative direction

                if hysteresis is >0
                correction is done when moving in the positive direction
            '''

            self.hysteresis_value = self.config_get(config,
                                                    'Motion',
                                                    'hysteresis',
                                                    cast='int')
        #             self.use_hysteresis = True
        #         else:
        #             self.use_hysteresis = False

        if config.has_option('General', 'initialize'):
            self.use_initialize = self.config_get(config,
                                                  'General',
                                                  'initialize',
                                                  cast='boolean')

        mi = self.min
        ma = self.max

        if self.sign == -1:
            tm = mi
            mi = ma
            ma = tm

        self.linear_mapper = LinearMapper(low_data=mi,
                                          high_data=ma,
                                          low_step=int(self.min_steps),
                                          high_step=int(self.steps))
コード例 #2
0
    def linear_mapper_factory(self):
        mi = self.min
        ma = self.max

        if self.sign == -1:
            mi, ma = ma, mi

        self.linear_mapper = LinearMapper(low_data=mi,
                                          high_data=ma,
                                          low_step=int(self.min_steps),
                                          high_step=int(self.steps))
コード例 #3
0
    def setUp(self):
        self.mapper = LinearMapper()

        self.mapper.low_data = 2
        self.mapper.high_data = 6
        self.mapper.low_step = 0
        self.mapper.high_step = 9500
コード例 #4
0
ファイル: kerr_motor.py プロジェクト: UManPychron/pychron
    def load_additional_args(self, config):
        """
        """
        args = [
            ('Motion', 'steps', 'int'),
            ('Motion', 'min_steps', 'int'),
            ('Motion', 'sign'),
            ('Motion', 'velocity'),
            ('Motion', 'acceleration'),

            ('Homing', 'home_delay'),
            ('Homing', 'home_velocity'),
            ('Homing', 'home_acceleration'),
            ('Homing', 'home_at_startup', 'boolean'),
            ('General', 'min'),
            ('General', 'max'),
            ('General', 'nominal_position'),
            ('General', 'units')
        ]
        for args in args:
            if len(args) == 3:
                section, key, cast = args
            else:
                cast = 'float'
                section, key = args

            self.set_attribute(config, key, section, key, cast=cast)

        if config.has_option('Motion', 'hysteresis'):
            '''
                if hysteresis is <0
                correction is done when moving in the negative direction

                if hysteresis is >0
                correction is done when moving in the positive direction
            '''

            self.hysteresis_value = self.config_get(config, 'Motion', 'hysteresis', cast='int')
        #             self.use_hysteresis = True
        #         else:
        #             self.use_hysteresis = False

        if config.has_option('General', 'initialize'):
            self.use_initialize = self.config_get(config, 'General', 'initialize', cast='boolean')

        mi = self.min
        ma = self.max

        if self.sign == -1:
            tm = mi
            mi = ma
            ma = tm

        self.linear_mapper = LinearMapper(
            low_data=mi,
            high_data=ma,
            low_step=int(self.min_steps),
            high_step=int(self.steps)
        )
コード例 #5
0
ファイル: base_linear_drive.py プロジェクト: NMGRL/pychron
    def linear_mapper_factory(self):
        mi = self.min
        ma = self.max

        if self.sign == -1:
            mi, ma = ma, mi

        self.linear_mapper = LinearMapper(
            low_data=mi,
            high_data=ma,
            low_step=int(self.min_steps),
            high_step=int(self.steps))
コード例 #6
0
ファイル: base_linear_drive.py プロジェクト: NMGRL/pychron
class BaseLinearDrive(ConsumerMixin):
    velocity = Property
    _velocity = Float
    acceleration = Float

    nominal_position = Float

    data_position = Property(depends_on='_data_position')
    _data_position = Float

    update_position = Float

    min = Float(0)
    max = Float(100)
    steps = Int(137500)
    min_steps = Int(0)
    sign = Float
    units = Str('mm')

    linear_mapper = Instance(LinearMapper)

    home_delay = Float
    home_velocity = Float
    home_acceleration = Float
    homing_position = Int
    home_at_startup = Bool(True)
    home_position = CInt
    home_limit = CInt

    timer = None
    progress = None

    _not_moving_count = 0
    unique_id = Str

    enabled = Bool(False)

    def set_position(self, *args, **kw):
        return self.set_value(*args, **kw)

    def set_value(self, value, block=False):
        if self.data_position != value:
            self.enabled = False
            value = self._convert_value(value)
            self.info('setting data position {}'.format(value))
            self._set_motor(value)
            #            self.data_position = value
            if block:
                self.info('waiting for move to complete')
                self.block()
                self.info('move complete')
                self.enabled = True
        else:
            self.info('not changing pos {}=={}'.format(self.data_position, value))

        return True

    def linear_mapper_factory(self):
        mi = self.min
        ma = self.max

        if self.sign == -1:
            mi, ma = ma, mi

        self.linear_mapper = LinearMapper(
            low_data=mi,
            high_data=ma,
            low_step=int(self.min_steps),
            high_step=int(self.steps))

    def block(self, n=3, tolerance=1, progress=None, homing=False):
        """
        """
        fail_cnt = 0
        pos_buffer = []

        while 1:
            if not self.is_simulation():
                break

            steps = self.load_data_position(set_pos=False)
            if steps is None:
                fail_cnt += 1
                if fail_cnt > 5:
                    break
                continue

            if homing:
                self.homing_position = steps
                # invoke_in_main_thread(self.trait_set, homing_position=steps)

            if progress is not None:
                progress.change_message('{} position = {}'.format(self.name, steps),
                                        auto_increment=False)

            pos_buffer.append(steps)
            if len(pos_buffer) == n:
                if abs(float(sum(pos_buffer)) / n - steps) < tolerance:
                    break
                else:
                    pos_buffer.pop(0)

            time.sleep(0.1)

        if fail_cnt > 5:
            self.warning('Problem Communicating')

    def load_data_position(self, set_pos=True):
        """
        """
        steps = self.read_position()

        if steps is not None:
            pos = self.linear_mapper.map_data(steps)
            pos = max(self.min, min(self.max, pos))
            self.update_position = pos
            if set_pos:
                self._data_position = pos

            self.debug('Load data position {} {} steps= {}'.format(
                pos, self.units,
                steps))

        return steps

    def read_position(self, verbose=False):
        steps = self._read_motor_position(verbose=verbose)
        return steps

    def timer_factory(self):
        """
            reuse timer if possible
        """
        timer = self.timer

        func = self._update_position
        if timer is None:
            self._not_moving_count = 0
            timer = Timer(250, func)
        else:
            if timer.isActive():
                self.debug('reusing old timer')
            else:
                self._not_moving_count = 0
                timer = Timer(250, func)

        return timer

    # private
    def _set_motor(self, value):
        return

    def _convert_value(self, value):
        return value

    def _read_motor_position(self, *args, **kw):
        return None

    def _load_config_attributes(self, config, args):
        for args in args:
            if len(args) == 3:
                section, key, cast = args
            else:
                cast = 'float'
                section, key = args

            self.set_attribute(config, key, section, key, cast=cast)

    # homing
    def _set_last_known_position(self, pos):
        self.debug('Set last known position: {}'.format(pos))
        hd = self._get_homing_persistence()
        if not hd:
            hd = {}

        hd['last_known_position'] = pos
        self._dump_homing_persistence(hd)

    def _get_last_known_position(self):
        hd = self._get_homing_persistence()
        if hd:
            return hd.get('last_known_position')

    @property
    def homing_path(self):
        return os.path.join(paths.hidden_dir, '{}-{}-homing.p'.format(self.name, self.unique_id))

    def _dump_homing_persistence(self, hd):
        p = self.homing_path
        with open(p, 'wb') as wfile:
            pickle.dump(hd, wfile)

    def _get_homing_persistence(self):
        p = self.homing_path
        if os.path.isfile(p):
            with open(p, 'rb') as rfile:
                return pickle.load(rfile)

    def _get_homing_required(self):
        hd = self._get_homing_persistence()
        if hd:
            return hd['homing_required']

    def _get_data_position(self):
        return self._data_position

    def _set_data_position(self, pos):
        self.add_consumable((self._set_motor, pos))

    def _get_velocity(self):
        return self._velocity

    def _set_velocity(self, v):
        self._velocity = v
コード例 #7
0
class KerrMotor(KerrDevice, ConsumerMixin):
    """
        Base class for motors controller by a kerr microcontroller board

    """
    use_initialize = Bool(True)
    use_hysteresis = Bool(False)
    hysteresis_value = Float(0)  # nominal value to for hysteresis

    velocity = Property
    _velocity = Float
    acceleration = Float
    home_delay = Float
    home_velocity = Float
    home_acceleration = Float
    home_position = Float(0)
    home_at_startup = Bool(True)
    min = Float(0)
    max = Float(100)
    steps = Int(137500)
    min_steps = Int(0)
    sign = Float

    enabled = Bool(False)
    timer = None

    data_position = Property(depends_on='_data_position')
    _data_position = Float

    update_position = Float
    nominal_position = Float

    progress = None

    _motor_position = CInt
    doing_hysteresis_correction = False
    do_hysteresis = False
    display_name = Property
    display_name_color = 'brown'

    locked = False
    units = 'mm'

    home_button = Button('Home')
    homing_position = Int
    home_status = Int

    def _get_display_name(self):
        return self.name.capitalize()

    def _build_hexstr(self, *hxlist):
        hexfmt = lambda a: '{{:0{}x}}'.format(a[1]).format(a[0])
        return ''.join(map(hexfmt, hxlist))

    def _build_io(self):
        return '1800'

    def _build_gains(self):
        '''
            F6  B004 2003 F401 E803 FF 00 E803 01 01 01
            cmd p    d    i    il   ol cl el   sr db sm

            B004 2003 F401 B004 FF 00 6400 010101

            100 1000 0 0 255 0 4000 1 1 1
        '''
        #        p = (45060, 4)
        #        d = (8195, 4)
        #        i = (62465, 4)
        #        il = (59395, 4)
        #        ol = (255, 2)
        #        cl = (0, 2)
        #        el = (59395, 4)
        #        sr = (1, 2)
        #        db = (1, 2)
        #        sm = (1, 2)
        p = (100, 4)
        d = (1000, 4)
        i = (0, 4)
        il = (0, 4)
        ol = (255, 2)
        cl = (0, 2)
        el = (4000, 4)
        sr = (1, 2)
        db = (1, 2)
        sm = (1, 2)
        gains = self._build_hexstr(p, d, i, il, ol, cl, el, sr, db, sm)
        return 'F6{}'.format(gains)

    #        hexfmt = lambda a: '{{:0{}x}}'.format(a[1]).format(a[0])
    #        return ''.join(['F6'] + map(hexfmt, [p, d, i, il, ol, cl, el, sr, db, sm]))
    def set_value(self, value, block=False):
        if self.data_position != value:
            self.enabled = False
            value = self._convert_value(value)
            self.info('setting data position {}'.format(value))
            self._set_motor(value)
            #            self.data_position = value
            if block:
                self.info('waiting for move to complete')
                self.block()
                self.info('move complete')
            self.enabled = True
        else:
            self.info('not changing pos {}=={}'.format(self.data_position,
                                                       value))

        return True

    def _convert_value(self, value):
        return value

    def load_additional_args(self, config):
        """
        """
        args = [('Motion', 'steps', 'int'), ('Motion', 'min_steps', 'int'),
                ('Motion', 'sign'), ('Motion', 'velocity'),
                ('Motion', 'acceleration'), ('Homing', 'home_delay'),
                ('Homing', 'home_velocity'), ('Homing', 'home_acceleration'),
                ('Homing', 'home_at_startup', 'boolean'), ('General', 'min'),
                ('General', 'max'), ('General', 'nominal_position'),
                ('General', 'units')]
        for args in args:
            if len(args) == 3:
                section, key, cast = args
            else:
                cast = 'float'
                section, key = args

            self.set_attribute(config, key, section, key, cast=cast)

        if config.has_option('Motion', 'hysteresis'):
            '''
                if hysteresis is <0
                correction is done when moving in the negative direction

                if hysteresis is >0
                correction is done when moving in the positive direction
            '''

            self.hysteresis_value = self.config_get(config,
                                                    'Motion',
                                                    'hysteresis',
                                                    cast='int')
        #             self.use_hysteresis = True
        #         else:
        #             self.use_hysteresis = False

        if config.has_option('General', 'initialize'):
            self.use_initialize = self.config_get(config,
                                                  'General',
                                                  'initialize',
                                                  cast='boolean')

        mi = self.min
        ma = self.max

        if self.sign == -1:
            tm = mi
            mi = ma
            ma = tm

        self.linear_mapper = LinearMapper(low_data=mi,
                                          high_data=ma,
                                          low_step=int(self.min_steps),
                                          high_step=int(self.steps))

    def _start_initialize(self, *args, **kw):
        '''
        '''
        self.info('init {}'.format(self.name))

    #        progress = kw['progress'] if 'progress' in kw else None
    #        if progress is not None:
    #            progress.change_message('Initialize {}'.format(self.name))
    #            self.progress = progress
    #            progress.increment()

    def _update_position_changed(self):
        #        self.debug('*****************************update position {}'.format(self.update_position))
        try:
            self.progress.change_message('{} position = {}'.format(
                self.name, self.update_position),
                                         auto_increment=False)
        except AttributeError:
            # self.progress is None
            pass

    def _finish_initialize(self):
        '''
        '''
        pass

    #         if self.home_at_startup:
    #             self._data_position = self.min
    #             self.update_position = self.min
    #             if self.sign == -1:
    #                 self._data_position = self.max
    #                 self.update_position = self.max

    def initialize(self, *args, **kw):
        '''
        '''
        try:
            self.progress = kw['progress']
        except KeyError:
            pass

        self._start_initialize(*args, **kw)
        self._initialize_(*args, **kw)
        self._finish_initialize()

        if self.nominal_position is not None:
            move_to_nominal = True
            if not globalv.ignore_initialization_questions:
                move_to_nominal = self.confirmation_dialog(
                    'Would you like to set the {} motor to its nominal pos of {}'
                    .format(self.name.upper(), self.nominal_position))

            if move_to_nominal:
                # move to the home position
                np = self.nominal_position
                self._set_motor(self.nominal_position, main=False)

                timed_out = False
                if self.timer:
                    timeout = 60
                    # timer stops when move has completed
                    timed_out = self.timer.wait_for_completion(timeout=timeout)

                if timed_out:
                    msg = 'move to nominal position {} timed out after {}s'.format(
                        np, timeout)
                    self.warning(msg)
                    self.warning_dialog(msg)
                else:
                    self.info(
                        'move to nominal position {} complete'.format(np))

                #                 self._set_data_position(self.nominal_position)
                #                 self.block(5, progress=self.progress)

        # remove reference to progress
        self.progress = None
        self.enabled = True

        self.setup_consumer(buftime=500)

        return True

    def _clear_bits(self):
        cmd = (self.address, '0b', 100, 'clear bits')
        self._execute_hex_command(cmd)

    def _initialize_motor(self, commands, *args, **kw):
        self.load_data_position()

        self._execute_hex_commands(commands)
        if self.home_at_startup:
            self.info('============ HOME AT STARTUP =============')
            self._execute_hex_commands([(self.address, '00', 100,
                                         'reset position')])
            self._home_motor(*args, **kw)

        self.load_data_position()

    def _initialize_(self, *args, **kw):
        '''
        '''
        addr = self.address
        commands = [
            (addr, '1706', 100, 'stop motor, turn off amp'),
            (addr, self._build_io(), 100, 'configure io pins'),
            (addr, self._build_gains(), 100, 'set gains'),
            (addr, '1701', 100, 'turn on amp'),
            #                     (addr, '00', 100, 'reset position'),
            #                     (addr, '1201', 100, 'set status')
            #                   (addr, '0b', 100, 'clear bits')
        ]
        self._initialize_motor(commands, *args, **kw)

    #         self._execute_hex_commands(commands)
    #
    #         if self.home_at_startup:
    #             self._execute_hex_commands([(addr, '00', 100, 'reset position')])
    #             self._home_motor(*args, **kw)
    #         else:
    #             commands = [(addr, '1701', 100, 'turn on amp'), ]
    #             self._execute_hex_commands(commands)

    def _home_button_fired(self):
        #         self.progress = myProgressDialog(max=2)
        #         self.progress.open()
        self.home_status = 1
        self._home_motor()
        self.load_data_position()
        self.home_status = 2
        self.information_dialog('Homing Complete')
        self.home_status = 0

    def _home_motor(self, progress=None, *args, **kw):
        '''
        '''
        if progress is not None:
            progress.increase_max()
            progress.change_message('Homing {}'.format(self.name))

        addr = self.address

        cmd = '94'
        control = 'F6'

        v = self._float_to_hexstr(self.home_velocity)
        a = self._float_to_hexstr(self.home_acceleration)
        move_cmd = ''.join((cmd, control, v, a))

        #         home_control_byte = self._load_home_control_byte()
        #         home_cmd = '19{:02x}'.format(home_control_byte)

        cmds = [
            #                 (addr, home_cmd, 100, '=======Set Homing===='),
            (addr, move_cmd, 100, 'Send to Home')
        ]
        self._execute_hex_commands(cmds)
        '''
            this is a hack. Because the home move is in velocity profile mode we cannot use the 0bit of the status byte to
            determine when the move is complete (0bit indicates when motor has reached requested velocity).

            instead we will poll the motor position until n successive positions are equal ie at a limt
        '''

        self.block(4, progress=progress, homing=True)

        # we are homed and should reset position

        cmds = [(addr, '00', 100, 'reset position')]

        self._execute_hex_commands(cmds)

    def reset_position(self, motor_off=True):
        '''
            1707 amp on
            1706 amp off
        '''
        addr = self.address
        b = '6' if motor_off else '7'
        cmds = [(addr, '170{}'.format(b), 100, 'Stop motor'),
                (addr, '00', 100, 'Reset Position')]
        self._execute_hex_commands(cmds)
        self._motor_position = 0

    def is_moving(self):
        return not self.enabled

    def progress_update(self, progress, signal):
        while not signal.is_set():
            pos = self._get_motor_position(verbose=False)
            if progress is not None:
                progress.change_message('{} position= {}'.format(
                    self.name, pos),
                                        auto_increment=False)
                #                 do_after(25, progress.change_message, '{} position = {}'.format(self.name, pos))
            time.sleep(0.5)

    def block(self, n=3, tolerance=1, progress=None, homing=False):
        '''
        '''
        fail_cnt = 0
        pos_buffer = []

        while not self.parent.simulation:

            steps = self.load_data_position(set_pos=False)
            if homing:
                invoke_in_main_thread(self.trait_set, home_position=steps)

            if progress is not None:
                progress.change_message('{} position = {}'.format(
                    self.name, steps),
                                        auto_increment=False)

            if steps is None:
                fail_cnt += 1
                if fail_cnt > 5:
                    break
                continue

            pos_buffer.append(steps)
            if len(pos_buffer) == n:
                if abs(float(sum(pos_buffer)) / n - steps) < tolerance:
                    break
                else:
                    pos_buffer.pop(0)

            time.sleep(0.1)

        if fail_cnt > 5:
            self.warning('Problem Communicating')

    def read_status(self, cb, verbose=True):
        if isinstance(cb, str):
            cb = '{:02x}'.format(int(cb, 2))

        addr = self.address
        cb = '13{}'.format(cb)
        cmd = self._build_command(addr, cb)
        status_byte = self.ask(cmd,
                               is_hex=True,
                               delay=100,
                               nbytes=3,
                               verbose=verbose,
                               info='get status byte')
        return status_byte

    def read_defined_status(self, verbose=True):

        addr = self.address
        cmd = '0E'
        cmd = self._build_command(addr, cmd)
        status_byte = self.ask(cmd,
                               is_hex=True,
                               delay=100,
                               nbytes=2,
                               info='get defined status',
                               verbose=verbose)
        return status_byte

    def load_data_position(self, set_pos=True):
        '''
        '''
        steps = self._get_motor_position(verbose=False)
        if steps is not None:
            pos = self.linear_mapper.map_data(steps)
            pos = max(self.min, min(self.max, pos))
            self.update_position = pos
            if set_pos:
                self._data_position = pos

            self.debug('Load data position {} {} steps= {}'.format(
                pos,
                self.units,
                steps,
            ))
            return steps

    def _moving(self, verbose=True):
        status_byte = self.read_defined_status(verbose=verbose)

        if status_byte in ('simulation', None):
            status_byte = 'DFDF'
        status_register = map(int, make_bitarray(int(status_byte[:2], 16)))
        return not status_register[7]

    def _get_motor_position(self, **kw):
        '''
        '''
        addr = self.address
        cmd = '13'
        control = '01'

        cmd = ''.join((cmd, control))
        cmd = (addr, cmd, 100, '')

        pos = self._execute_hex_command(cmd, nbytes=6, **kw)

        # trim off status and checksum bits
        if pos is not None:
            pos = pos[2:-2]
            pos = self._hexstr_to_float(pos)
            #            self._motor_position = pos
            return pos

    def _load_home_control_byte(self):
        '''
           control byte
                7 6 5 4 3 2 1 0
            97- 1 0 0 1 0 1 1 1
            0=capture home on limit1
            1=capture home on limit2
            2=turn motor off on home
            3=capture home on home
            4=stop abruptly
            5=stop smoothly
            6,7=not used- clear to 0
        '''

        return int('00010011', 2)

    def _load_trajectory_controlbyte(self):
        '''
           control byte
                7 6 5 4 3 2 1 0
            97- 1 0 0 1 0 1 1 1

            0=load pos
            1=load vel
            2=load acce
            3=load pwm
            4=enable servo
            5=profile mode 0=trap 1=vel
            6=direction trap mode 0=abs 1=rel vel mode 0=for. 1=back
            7=start motion now

        '''

        return '{:02x}'.format(int('10010111', 2))

    def _calculate_hysteresis_position(self, pos, hysteresis):
        hpos = pos + hysteresis
        #         self._hysteresis_correction = 0
        #         if hysteresis:
        hpos = max(self.min_steps, min(self.steps, hpos))
        #             if hpos > self.max:
        #                 self._hysteresis_correction = hpos - self.max
        #                 hpos = self.max
        #             elif hpos < self.min:
        #                 self._hysteresis_correction = hpos - self.min
        #                 hpos = self.min
        #             else:
        #                 self._hysteresis_correction = hysteresis
        return hpos

    def _set_motor_position_(self, pos, hysteresis=0, velocity=None):
        '''
        '''
        hpos = self._calculate_hysteresis_position(pos, hysteresis)
        self._desired_position = pos
        self._motor_position = hpos
        #        self._motor_position =npos= min(self.max, max(self.min, pos + hysteresis))
        #============pos is in mm===========
        addr = self.address
        cmd = 'D4'
        control = self._load_trajectory_controlbyte()
        position = self._float_to_hexstr(hpos)
        if velocity is None:
            velocity = self.velocity
        v = self._float_to_hexstr(velocity)

        a = self._float_to_hexstr(self.acceleration)
        #        print cmd, control, position, v, a
        cmd = ''.join((cmd, control, position, v, a))
        cmd = (addr, cmd, 100, 'setting motor steps {}'.format(hpos))

        self._execute_hex_command(cmd)

    def _update_position(self):
        '''
        '''

        if self._moving(verbose=False):
            self.enabled = False

        #        if not self._check_status_byte(0):
        #            self.enabled = False

        else:
            #             if self.use_hysteresis and \
            if self.hysteresis_value and \
                    not self.doing_hysteresis_correction and \
                    self.do_hysteresis:
                # move to original desired position at half velocity
                #                    print 'mp',self._motor_position, self.hysteresis_value
                self._set_motor_position_(self._desired_position,
                                          velocity=self.velocity / 2)
                self.doing_hysteresis_correction = True
            else:
                self.enabled = True
                if self.timer is not None:
                    self.timer.Stop()
                    #                     self.update_position = self._data_position
                    time.sleep(0.25)
                    self.load_data_position(set_pos=False)

        if not self.enabled:
            self.load_data_position(set_pos=False)

    def _get_data_position(self):
        '''
        '''
        return self._data_position

    def _set_data_position(self, pos):
        self.add_consumable((self._set_motor, pos))

    def _set_motor(self, pos, main=True):
        #         print self._data_position, pos
        if self._data_position != pos or not self._data_position:
            self.info('setting motor in data space {:0.3f}'.format(float(pos)))

            self._data_position = pos
            lm = self.linear_mapper
            steps = lm.map_steps(pos)

            hv = 0
            hysteresis = self.hysteresis_value
            if hysteresis:
                self.do_hysteresis = False
                if hysteresis < 0:
                    use_hysteresis = self._motor_position > steps
                else:
                    use_hysteresis = self._motor_position < steps

                if use_hysteresis:
                    self.do_hysteresis = True
                    self.doing_hysteresis_correction = False
                    hv = hysteresis

            self._set_motor_position_(steps, hv)

            def launch():
                self.timer = self.timer_factory()

            if main:
                invoke_in_main_thread(launch)
            else:
                launch()

            #            if self.parent.simulation:
            #                self.update_position = self._data_position

    def timer_factory(self):
        """
            reuse timer if possible
        """
        timer = self.timer

        func = self._update_position
        if timer is None:
            self._not_moving_count = 0
            timer = Timer(250, func)
        else:
            if timer.isActive():
                self.debug('reusing old timer')
            else:
                self._not_moving_count = 0
                timer = Timer(250, func)

        return timer

    def _float_to_hexstr(self, f, endianness='little'):
        '''
        '''
        f = max(0, f)
        fmt = '%sI' % ('<' if endianness == 'little' else '>')
        return binascii.hexlify(struct.pack(fmt, int(f)))

    def _hexstr_to_float(self, h, endianness='little'):
        '''
        '''
        fmt = '%sI' % ('<' if endianness == 'little' else '>')
        try:
            return struct.unpack(fmt, h.decode('hex'))[0]
        except Exception, e:
            print e
コード例 #8
0
ファイル: kerr_motor.py プロジェクト: OSUPychron/pychron
class KerrMotor(KerrDevice, ConsumerMixin):
    """
        Base class for motors controller by a kerr microcontroller board

    """
    use_initialize = Bool(True)
    use_hysteresis = Bool(False)
    hysteresis_value = Float(0)  # nominal value to for hysteresis

    velocity = Property
    _velocity = Float
    acceleration = Float
    home_delay = Float
    home_velocity = Float
    home_acceleration = Float
    homing_position = Int
    home_at_startup = Bool(True)
    home_position = CInt
    home_limit = CInt

    min = Float(0)
    max = Float(100)
    steps = Int(137500)
    min_steps = Int(0)
    sign = Float

    enabled = Bool(False)
    timer = None

    data_position = Property(depends_on='_data_position')
    _data_position = Float

    update_position = Float
    nominal_position = Float

    progress = None

    _motor_position = CInt
    doing_hysteresis_correction = False
    do_hysteresis = False
    display_name = Property
    display_name_color = 'brown'

    locked = False
    units = 'mm'

    home_button = Button('Home')

    home_status = Int

    def set_value(self, value, block=False):
        if self.data_position != value:
            self.enabled = False
            value = self._convert_value(value)
            self.info('setting data position {}'.format(value))
            self._set_motor(value)
            #            self.data_position = value
            if block:
                self.info('waiting for move to complete')
                self.block()
                self.info('move complete')
                self.enabled = True
        else:
            self.info('not changing pos {}=={}'.format(self.data_position, value))

        return True

    def load_additional_args(self, config):
        """
        """
        args = [
            ('Motion', 'steps', 'int'),
            ('Motion', 'min_steps', 'int'),
            ('Motion', 'sign'),
            ('Motion', 'velocity'),
            ('Motion', 'acceleration'),

            ('Homing', 'home_delay'),
            ('Homing', 'home_velocity'),
            ('Homing', 'home_acceleration'),
            ('Homing', 'home_at_startup', 'boolean'),
            ('Homing', 'home_position'),
            ('Homing', 'home_limit'),

            ('General', 'min'),
            ('General', 'max'),
            ('General', 'nominal_position'),
            ('General', 'units')
        ]
        for args in args:
            if len(args) == 3:
                section, key, cast = args
            else:
                cast = 'float'
                section, key = args

            self.set_attribute(config, key, section, key, cast=cast)

        if config.has_option('Motion', 'hysteresis'):
            '''
                if hysteresis is <0
                correction is done when moving in the negative direction

                if hysteresis is >0
                correction is done when moving in the positive direction
            '''

            self.hysteresis_value = self.config_get(config, 'Motion', 'hysteresis', cast='int')
        # self.use_hysteresis = True
        #         else:
        #             self.use_hysteresis = False

        if config.has_option('General', 'initialize'):
            self.use_initialize = self.config_get(config, 'General', 'initialize', cast='boolean')

        mi = self.min
        ma = self.max

        if self.sign == -1:
            tm = mi
            mi = ma
            ma = tm

        self.linear_mapper = LinearMapper(
            low_data=mi,
            high_data=ma,
            low_step=int(self.min_steps),
            high_step=int(self.steps))

    def initialize(self, *args, **kw):
        """
        """
        try:
            self.progress = kw['progress']
        except KeyError:
            pass

        self._start_initialize(*args, **kw)
        self._initialize(*args, **kw)
        self._finish_initialize()

        if self.nominal_position is not None:
            move_to_nominal = True
            if not globalv.ignore_initialization_questions:
                move_to_nominal = self.confirmation_dialog(
                    'Would you like to set the {} motor to its nominal pos of {}'.format(self.name.upper(),
                                                                                         self.nominal_position))

            if move_to_nominal:
                # move to the home position
                np = self.nominal_position
                self._set_motor(self.nominal_position, main=False)

                timed_out = False
                if self.timer:
                    timeout = 60
                    # timer stops when move has completed
                    timed_out = self.timer.wait_for_completion(timeout=timeout)

                if timed_out:
                    msg = 'move to nominal position {} timed out after {}s'.format(np, timeout)
                    self.warning(msg)
                    self.warning_dialog(msg)
                else:
                    self.info('move to nominal position {} complete'.format(np))

                    #                 self._set_data_position(self.nominal_position)
                    #                 self.block(5, progress=self.progress)

        # remove reference to progress
        self.progress = None
        self.enabled = True

        self.setup_consumer(buftime=500)

        return True

    def reset_position(self, motor_off=True, position=None):
        """
            1707 amp on
            1706 amp off
        """
        addr = self.address
        # b = '6' if motor_off else '7'

        cmds = []
        if motor_off:
            # cmd, msg = '1706'
            cmds.append((addr, '1706', 100, 'Motor OFF'))

        if position is not None:
            cmd, msg = '50{:04X}'.format(position), 'Setting home position as {}'.format(position)
        else:
            cmd, msg = '00', 'Reset Position'

        cmds.append((addr, cmd, 100, msg))
        if motor_off:
            cmds.append((addr, '1707', 100, 'Moto ON'))

            # if motor_off:
            # cmds = [(addr, '', 100, )]

        # cmds = [(addr, '170{}'.format(b), 100, 'Stop motor'),
        #         (addr, cmd, 100, msg),
        #         ()]
        self._execute_hex_commands(cmds)
        # self._motor_position = 0

    # PIC Commands
    def read_status(self, cb, verbose=True):
        if isinstance(cb, str):
            cb = '{:02x}'.format(int(cb, 2))

        addr = self.address
        cb = '13{}'.format(cb)
        cmd = self._build_command(addr, cb)
        status_byte = self.ask(cmd, is_hex=True,
                               delay=100,
                               nbytes=3,
                               verbose=verbose,
                               info='get status byte')
        return status_byte

    def read_defined_status(self, verbose=True):

        addr = self.address
        cmd = '0E'
        cmd = self._build_command(addr, cmd)
        status_byte = self.ask(cmd, is_hex=True,
                               delay=100,
                               nbytes=2,
                               info='get defined status',
                               verbose=verbose)
        return status_byte

    def read_home_position(self):
        addr = self.address

        cb = int('00010000', 2)
        cmd = '13{:02X}'.format(cb)
        cmd = self._build_command(addr, cmd)
        position_byte = self.ask(cmd, is_hex=True,
                                 delay=100,
                                 nbytes=6,
                                 info='get home position',
                                 verbose=True)
        return self._parse_position(position_byte)

    def is_moving(self):
        return not self.enabled

    def progress_update(self, progress, signal):
        while not signal.is_set():
            pos = self._read_motor_position(verbose=False)
            if progress is not None:
                progress.change_message('{} position= {}'.format(self.name, pos),
                                        auto_increment=False)
                #                 do_after(25, progress.change_message, '{} position = {}'.format(self.name, pos))
            time.sleep(0.5)

    def block(self, n=3, tolerance=1, progress=None, homing=False):
        """
        """
        fail_cnt = 0
        pos_buffer = []

        while not self.parent.simulation:

            steps = self.load_data_position(set_pos=False)
            if homing:
                invoke_in_main_thread(self.trait_set, homing_position=steps)

            if progress is not None:
                progress.change_message('{} position = {}'.format(self.name, steps),
                                        auto_increment=False)

            if steps is None:
                fail_cnt += 1
                if fail_cnt > 5:
                    break
                continue

            pos_buffer.append(steps)
            if len(pos_buffer) == n:
                if abs(float(sum(pos_buffer)) / n - steps) < tolerance:
                    break
                else:
                    pos_buffer.pop(0)

            time.sleep(0.1)

        if fail_cnt > 5:
            self.warning('Problem Communicating')

    def load_data_position(self, set_pos=True):
        """
        """
        steps = self._read_motor_position(verbose=False)
        if steps is not None:
            pos = self.linear_mapper.map_data(steps)
            pos = max(self.min, min(self.max, pos))
            self.update_position = pos
            if set_pos:
                self._data_position = pos

            self.debug('Load data position {} {} steps= {}'.format(
                pos, self.units,
                steps))
            return steps

    def timer_factory(self):
        """
            reuse timer if possible
        """
        timer = self.timer

        func = self._update_position
        if timer is None:
            self._not_moving_count = 0
            timer = Timer(250, func)
        else:
            if timer.isActive():
                self.debug('reusing old timer')
            else:
                self._not_moving_count = 0
                timer = Timer(250, func)

        return timer

    def set_homing_required(self, value):
        self.debug('Set homing required: {}'.format(value))
        hd = self._get_homing_persistence()
        if not hd:
            hd = {}

        hd['homing_required'] = value
        self._dump_homing_persistence(hd)

    # private
    def _initialize(self, *args, **kw):
        addr = self.address
        commands = [(addr, '1706', 100, 'stop motor, turn off amp'),
                    (addr, self._build_io(), 100, 'configure io pins'),
                    (addr, self._build_gains(), 100, 'set gains'),
                    (addr, '1701', 100, 'turn on amp')]
        # (addr, '00', 100, 'reset position'),
        # (addr, '1201', 100, 'set status')
        # (addr, '0b', 100, 'clear bits')]
        self._initialize_motor(commands, *args, **kw)

    def _initialize_motor(self, commands, *args, **kw):
        self.load_data_position()

        self._execute_hex_commands(commands)

        homing_required = self._get_homing_required()
        self.debug('Homing required: {}'.format(homing_required))
        if self.home_at_startup:
            self.info('============ HOME AT STARTUP =============')
            self._execute_hex_commands([(self.address, '00', 100, 'reset position')])
            self._home_motor(*args, **kw)
        elif homing_required:
            pos = self._get_last_known_position()
            if pos is not None and self.confirmation_dialog('Last Known Position: {}. '
                                                            'Use this value instead of homing?'.format(pos)):
                self.reset_position(position=self.homing_position)

            else:
                self.info('============ HOME AT STARTUP (Required) =============')
                self._execute_hex_commands([(self.address, '00', 100, 'reset position')])
                self._home_motor(*args, **kw)

        elif self.homing_position:
            self.reset_position(position=self.homing_position)

        self.load_data_position()

    def _set_last_known_position(self, pos):
        self.debug('Set last known position: {}'.format(pos))
        hd = self._get_homing_persistence()
        if not hd:
            hd = {}

        hd['last_known_position'] = pos
        self._dump_homing_persistence(hd)

    def _get_last_known_position(self):
        hd = self._get_homing_persistence()
        if hd:
            return hd.get('last_known_position')

    @property
    def homing_path(self):
        return os.path.join(paths.hidden_dir, '{}-{}-homing.p'.format(self.name, self.address))

    def _dump_homing_persistence(self, hd):
        p = self.homing_path
        with open(p, 'w') as wfile:
            pickle.dump(hd, wfile)

    def _get_homing_persistence(self):
        p = self.homing_path
        if os.path.isfile(p):
            with open(p, 'r') as rfile:
                return pickle.load(rfile)

    def _get_homing_required(self):
        hd = self._get_homing_persistence()
        if hd:
            return hd['homing_required']

    def _home_motor(self, progress=None, *args, **kw):

        if progress is not None:
            progress.increase_max()
            progress.change_message('Homing {}'.format(self.name))

        addr = self.address

        cmd = '94'
        control = 'F6'

        v = self._float_to_hexstr(self.home_velocity)
        a = self._float_to_hexstr(self.home_acceleration)
        move_cmd = ''.join((cmd, control, v, a))

        home_control_byte = self._load_home_control_byte()
        home_cmd = '19{:02x}'.format(home_control_byte)
        self._clear_bits()
        cmds = [(addr, home_cmd, 100, '=======Set Homing===='),
                (addr, move_cmd, 100, 'Send to Home')]
        self._execute_hex_commands(cmds)

        self.debug('wait for home started')
        self._wait_for_home(progress)
        self.debug('wait for home complete')

        pos = self.read_home_position()
        self.debug('home position: {}'.format(pos))
        cmds = [(addr, '00', 100, 'reset position')]
        self._execute_hex_commands(cmds)

    def _wait_for_home(self, progress=None):
        # wait until homing signal set

        hbit = 5 if self.home_limit == 1 else 6
        psteps = None
        while 1:
            steps = self.load_data_position(set_pos=False)
            invoke_in_main_thread(self.trait_set, homing_position=steps)
            status = self.read_defined_status()

            if not self._test_status_byte(status, setbits=[7]):
                break
            if self._test_status_byte(status, setbits=[7, hbit]):
                break

            if steps == psteps:
                step_count += 1
            else:
                step_count = 0

            if step_count > 10:
                break
            psteps = steps

            time.sleep(0.25)
        self.debug('wait for home complete')

    def _parse_position(self, pos):
        if pos is not None:
            pos = pos[2:-2]
            return self._hexstr_to_float(pos)

    def _test_status_byte(self, status, setbits):
        b = '{:08b}'.format(int(status[:2], 16))
        bb = [bool(int(b[7 - si])) for si in setbits]

        return all(bb)

    def _moving(self, verbose=True):
        status_byte = self.read_defined_status(verbose=verbose)

        if status_byte in ('simulation', None):
            status_byte = 'DFDF'
        status_register = map(int, make_bitarray(int(status_byte[:2], 16)))
        return not status_register[7]

    def _clear_bits(self):
        cmd = (self.address, '0b', 100, 'clear bits')
        self._execute_hex_command(cmd)

    def _read_motor_position(self, **kw):
        addr = self.address
        cmd = '13'
        control = '01'

        cmd = ''.join((cmd, control))
        cmd = (addr, cmd, 100, '')

        pos = self._execute_hex_command(cmd, nbytes=6, **kw)

        # trim off status and checksum bits
        if pos is not None:
            pos = pos[2:-2]
            pos = self._hexstr_to_float(pos)
            return pos

    def _load_home_control_byte(self):
        """
           control byte
                7 6 5 4 3 2 1 0
            97- 1 0 0 1 0 1 1 1
            0=capture home on limit1
            1=capture home on limit2
            2=turn motor off on home
            3=capture home on home
            4=stop abruptly
            5=stop smoothly
            6,7=not used- clear to 0
        """
        if self.home_limit == 1:
            bs = '00010001'
        else:
            bs = '00010010'

        return int(bs, 2)

    def _load_trajectory_controlbyte(self):
        """
           control byte
                7 6 5 4 3 2 1 0
            97- 1 0 0 1 0 1 1 1

            0=load pos
            1=load vel
            2=load acce
            3=load pwm
            4=enable servo
            5=profile mode 0=trap 1=vel
            6=direction trap mode 0=abs 1=rel vel mode 0=for. 1=back
            7=start motion now

        """

        return '{:02x}'.format(int('10010111', 2))

    def _calculate_hysteresis_position(self, pos, hysteresis):
        hpos = pos + hysteresis

        hpos = max(self.min_steps, min(self.steps, hpos))

        return int(hpos)

    def _update_position(self):
        """
        """

        if self._moving(verbose=True):
            self.enabled = False
        else:
            if self.hysteresis_value and \
                    not self.doing_hysteresis_correction and \
                    self.do_hysteresis:
                # move to original desired position at half velocity
                self._set_motor_position(
                    self._desired_position,
                    velocity=self.velocity / 2)
                self.doing_hysteresis_correction = True
            else:
                self.enabled = True
                if self.timer is not None:
                    self.timer.Stop()
                    #                     self.update_position = self._data_position
                    time.sleep(0.25)

                steps = self.load_data_position(set_pos=False)
                self._set_last_known_position(steps)

        if not self.enabled:
            self.load_data_position(set_pos=False)

    def _set_motor(self, pos, main=True):
        if self._data_position != pos or not self._data_position:
            self.info('setting motor in data space {:0.3f}'.format(float(pos)))

            self._data_position = pos
            lm = self.linear_mapper
            steps = lm.map_steps(pos)

            hv = 0
            hysteresis = self.hysteresis_value
            if hysteresis:
                self.do_hysteresis = False
                if hysteresis < 0:
                    use_hysteresis = self._motor_position > steps
                else:
                    use_hysteresis = self._motor_position < steps

                if use_hysteresis:
                    self.do_hysteresis = True
                    self.doing_hysteresis_correction = False
                    hv = hysteresis

            self._set_motor_position(steps, hv)

            def launch():
                self.timer = self.timer_factory()

            if main:
                invoke_in_main_thread(launch)
            else:
                launch()

    def _set_motor_position(self, pos, hysteresis=0, velocity=None):
        """
        """
        hpos = self._calculate_hysteresis_position(pos, hysteresis)
        self._desired_position = pos
        self._motor_position = hpos
        #        self._motor_position =npos= min(self.max, max(self.min, pos + hysteresis))
        # ============pos is in mm===========
        addr = self.address
        cmd = 'D4'
        control = self._load_trajectory_controlbyte()
        position = self._float_to_hexstr(hpos)
        if velocity is None:
            velocity = self.velocity
        v = self._float_to_hexstr(velocity)

        a = self._float_to_hexstr(self.acceleration)
        #        print cmd, control, position, v, a
        cmd = ''.join((cmd, control, position, v, a))
        cmd = (addr, cmd, 100, 'setting motor steps {}'.format(hpos))

        self._execute_hex_command(cmd)

    def _convert_value(self, value):
        return value

    def _float_to_hexstr(self, f, endianness='little'):
        f = max(0, f)
        fmt = '%si' % ('<' if endianness == 'little' else '>')
        return binascii.hexlify(struct.pack(fmt, int(f)))

    def _hexstr_to_float(self, h, endianness='little'):
        # fmt = '%si'.('<' if endianness == 'little' else '>')
        fmt = '<i' if endianness == 'little' else '>i'
        try:
            return struct.unpack(fmt, h.decode('hex'))[0]
        except Exception, e:
            print 'exception', e
コード例 #9
0
class BaseLinearDrive(ConsumerMixin):
    velocity = Property
    _velocity = Float
    acceleration = Float

    nominal_position = Float

    data_position = Property(depends_on='_data_position')
    _data_position = Float

    update_position = Float

    min = Float(0)
    max = Float(100)
    steps = Int(137500)
    min_steps = Int(0)
    sign = Float
    units = Str('mm')

    linear_mapper = Instance(LinearMapper)

    home_delay = Float
    home_velocity = Float
    home_acceleration = Float
    homing_position = Int
    home_at_startup = Bool(True)
    home_position = CInt
    home_limit = CInt

    timer = None
    progress = None

    _not_moving_count = 0
    unique_id = Str

    enabled = Bool(False)

    def set_position(self, *args, **kw):
        return self.set_value(*args, **kw)

    def set_value(self, value, block=False):
        if self.data_position != value:
            self.enabled = False
            value = self._convert_value(value)
            self.info('setting data position {}'.format(value))
            self._set_motor(value)
            #            self.data_position = value
            if block:
                self.info('waiting for move to complete')
                self.block()
                self.info('move complete')
                self.enabled = True
        else:
            self.info('not changing pos {}=={}'.format(self.data_position, value))

        return True

    def linear_mapper_factory(self):
        mi = self.min
        ma = self.max

        if self.sign == -1:
            mi, ma = ma, mi

        self.linear_mapper = LinearMapper(
            low_data=mi,
            high_data=ma,
            low_step=int(self.min_steps),
            high_step=int(self.steps))

    def block(self, n=3, tolerance=1, progress=None, homing=False):
        """
        """
        fail_cnt = 0
        pos_buffer = []

        while 1:
            if not self.is_simulation():
                break

            steps = self.load_data_position(set_pos=False)
            if steps is None:
                fail_cnt += 1
                if fail_cnt > 5:
                    break
                continue

            if homing:
                self.homing_position = steps
                # invoke_in_main_thread(self.trait_set, homing_position=steps)

            if progress is not None:
                progress.change_message('{} position = {}'.format(self.name, steps),
                                        auto_increment=False)

            pos_buffer.append(steps)
            if len(pos_buffer) == n:
                if abs(float(sum(pos_buffer)) / n - steps) < tolerance:
                    break
                else:
                    pos_buffer.pop(0)

            time.sleep(0.1)

        if fail_cnt > 5:
            self.warning('Problem Communicating')

    def load_data_position(self, set_pos=True):
        """
        """
        steps = self.read_position()

        if steps is not None:
            pos = self.linear_mapper.map_data(steps)
            pos = max(self.min, min(self.max, pos))
            self.update_position = pos
            if set_pos:
                self._data_position = pos

            self.debug('Load data position {} {} steps= {}'.format(
                pos, self.units,
                steps))

        return steps

    def read_position(self, verbose=False):
        steps = self._read_motor_position(verbose=verbose)
        return steps

    def timer_factory(self):
        """
            reuse timer if possible
        """
        timer = self.timer

        func = self._update_position
        if timer is None:
            self._not_moving_count = 0
            timer = Timer(250, func)
        else:
            if timer.isActive():
                self.debug('reusing old timer')
            else:
                self._not_moving_count = 0
                timer = Timer(250, func)

        return timer

    # private
    def _set_motor(self, value):
        return

    def _convert_value(self, value):
        return value

    def _read_motor_position(self, *args, **kw):
        return None

    def _load_config_attributes(self, config, args):
        for args in args:
            if len(args) == 3:
                section, key, cast = args
            else:
                cast = 'float'
                section, key = args

            self.set_attribute(config, key, section, key, cast=cast)

    # homing
    def _set_last_known_position(self, pos):
        self.debug('Set last known position: {}'.format(pos))
        hd = self._get_homing_persistence()
        if not hd:
            hd = {}

        hd['last_known_position'] = pos
        self._dump_homing_persistence(hd)

    def _get_last_known_position(self):
        hd = self._get_homing_persistence()
        if hd:
            return hd.get('last_known_position')

    @property
    def homing_path(self):
        return os.path.join(paths.hidden_dir, '{}-{}-homing.p'.format(self.name, self.unique_id))

    def _dump_homing_persistence(self, hd):
        p = self.homing_path
        with open(p, 'w') as wfile:
            pickle.dump(hd, wfile)

    def _get_homing_persistence(self):
        p = self.homing_path
        if os.path.isfile(p):
            with open(p, 'r') as rfile:
                return pickle.load(rfile)

    def _get_homing_required(self):
        hd = self._get_homing_persistence()
        if hd:
            return hd['homing_required']

    def _get_data_position(self):
        return self._data_position

    def _set_data_position(self, pos):
        self.add_consumable((self._set_motor, pos))

    def _get_velocity(self):
        return self._velocity

    def _set_velocity(self, v):
        self._velocity = v
コード例 #10
0
ファイル: kerr_motor.py プロジェクト: UManPychron/pychron
class KerrMotor(KerrDevice, ConsumerMixin):
    """
        Base class for motors controller by a kerr microcontroller board

    """
    use_initialize = Bool(True)
    use_hysteresis = Bool(False)
    hysteresis_value = Float(0)  # nominal value to for hysteresis

    velocity = Property
    _velocity = Float
    acceleration = Float
    home_delay = Float
    home_velocity = Float
    home_acceleration = Float
    home_position = Float(0)
    home_at_startup = Bool(True)
    min = Float(0)
    max = Float(100)
    steps = Int(137500)
    min_steps = Int(0)
    sign = Float

    enabled = Bool(False)
    timer = None

    data_position = Property(depends_on='_data_position')
    _data_position = Float

    update_position = Float
    nominal_position = Float

    progress = None

    _motor_position = CInt
    doing_hysteresis_correction = False
    do_hysteresis = False
    display_name = Property
    display_name_color = 'brown'

    locked = False
    units = 'mm'

    home_button = Button('Home')
    homing_position = Int
    home_status = Int

    def _get_display_name(self):
        return self.name.capitalize()

    def _build_hexstr(self, *hxlist):
        hexfmt = lambda a: '{{:0{}x}}'.format(a[1]).format(a[0])
        return ''.join(map(hexfmt, hxlist))

    def _build_io(self):
        return '1800'

    def _build_gains(self):
        '''
            F6  B004 2003 F401 E803 FF 00 E803 01 01 01
            cmd p    d    i    il   ol cl el   sr db sm

            B004 2003 F401 B004 FF 00 6400 010101

            100 1000 0 0 255 0 4000 1 1 1
        '''
        #        p = (45060, 4)
        #        d = (8195, 4)
        #        i = (62465, 4)
        #        il = (59395, 4)
        #        ol = (255, 2)
        #        cl = (0, 2)
        #        el = (59395, 4)
        #        sr = (1, 2)
        #        db = (1, 2)
        #        sm = (1, 2)
        p = (100, 4)
        d = (1000, 4)
        i = (0, 4)
        il = (0, 4)
        ol = (255, 2)
        cl = (0, 2)
        el = (4000, 4)
        sr = (1, 2)
        db = (1, 2)
        sm = (1, 2)
        gains = self._build_hexstr(p, d, i, il, ol, cl, el, sr, db, sm)
        return 'F6{}'.format(gains)

    #        hexfmt = lambda a: '{{:0{}x}}'.format(a[1]).format(a[0])
    #        return ''.join(['F6'] + map(hexfmt, [p, d, i, il, ol, cl, el, sr, db, sm]))
    def set_value(self, value, block=False):
        if self.data_position != value:
            self.enabled = False
            value = self._convert_value(value)
            self.info('setting data position {}'.format(value))
            self._set_motor(value)
            #            self.data_position = value
            if block:
                self.info('waiting for move to complete')
                self.block()
                self.info('move complete')
            self.enabled = True
        else:
            self.info('not changing pos {}=={}'.format(self.data_position, value))

        return True

    def _convert_value(self, value):
        return value

    def load_additional_args(self, config):
        """
        """
        args = [
            ('Motion', 'steps', 'int'),
            ('Motion', 'min_steps', 'int'),
            ('Motion', 'sign'),
            ('Motion', 'velocity'),
            ('Motion', 'acceleration'),

            ('Homing', 'home_delay'),
            ('Homing', 'home_velocity'),
            ('Homing', 'home_acceleration'),
            ('Homing', 'home_at_startup', 'boolean'),
            ('General', 'min'),
            ('General', 'max'),
            ('General', 'nominal_position'),
            ('General', 'units')
        ]
        for args in args:
            if len(args) == 3:
                section, key, cast = args
            else:
                cast = 'float'
                section, key = args

            self.set_attribute(config, key, section, key, cast=cast)

        if config.has_option('Motion', 'hysteresis'):
            '''
                if hysteresis is <0
                correction is done when moving in the negative direction

                if hysteresis is >0
                correction is done when moving in the positive direction
            '''

            self.hysteresis_value = self.config_get(config, 'Motion', 'hysteresis', cast='int')
        #             self.use_hysteresis = True
        #         else:
        #             self.use_hysteresis = False

        if config.has_option('General', 'initialize'):
            self.use_initialize = self.config_get(config, 'General', 'initialize', cast='boolean')

        mi = self.min
        ma = self.max

        if self.sign == -1:
            tm = mi
            mi = ma
            ma = tm

        self.linear_mapper = LinearMapper(
            low_data=mi,
            high_data=ma,
            low_step=int(self.min_steps),
            high_step=int(self.steps)
        )

    def _start_initialize(self, *args, **kw):
        '''
        '''
        self.info('init {}'.format(self.name))

    #        progress = kw['progress'] if 'progress' in kw else None
    #        if progress is not None:
    #            progress.change_message('Initialize {}'.format(self.name))
    #            self.progress = progress
    #            progress.increment()

    def _update_position_changed(self):
    #        self.debug('*****************************update position {}'.format(self.update_position))
        try:
            self.progress.change_message('{} position = {}'.format(self.name, self.update_position),
                                         auto_increment=False)
        except AttributeError:
            # self.progress is None
            pass

    def _finish_initialize(self):
        '''
        '''
        pass

    #         if self.home_at_startup:
    #             self._data_position = self.min
    #             self.update_position = self.min
    #             if self.sign == -1:
    #                 self._data_position = self.max
    #                 self.update_position = self.max

    def initialize(self, *args, **kw):
        '''
        '''
        try:
            self.progress = kw['progress']
        except KeyError:
            pass

        self._start_initialize(*args, **kw)
        self._initialize_(*args, **kw)
        self._finish_initialize()

        if self.nominal_position is not None:
            move_to_nominal = True
            if not globalv.ignore_initialization_questions:
                move_to_nominal = self.confirmation_dialog(
                    'Would you like to set the {} motor to its nominal pos of {}'.format(self.name.upper(),
                                                                                         self.nominal_position))

            if move_to_nominal:
                # move to the home position
                np = self.nominal_position
                self._set_motor(self.nominal_position, main=False)

                timed_out = False
                if self.timer:
                    timeout = 60
                    # timer stops when move has completed
                    timed_out = self.timer.wait_for_completion(timeout=timeout)

                if timed_out:
                    msg = 'move to nominal position {} timed out after {}s'.format(np, timeout)
                    self.warning(msg)
                    self.warning_dialog(msg)
                else:
                    self.info('move to nominal position {} complete'.format(np))

                #                 self._set_data_position(self.nominal_position)
                #                 self.block(5, progress=self.progress)

        # remove reference to progress
        self.progress = None
        self.enabled = True

        self.setup_consumer(buftime=500)

        return True

    def _clear_bits(self):
        cmd = (self.address, '0b', 100, 'clear bits')
        self._execute_hex_command(cmd)

    def _initialize_motor(self, commands, *args, **kw):
        self.load_data_position()

        self._execute_hex_commands(commands)
        if self.home_at_startup:
            self.info('============ HOME AT STARTUP =============')
            self._execute_hex_commands([(self.address, '00', 100, 'reset position')])
            self._home_motor(*args, **kw)

        self.load_data_position()

    def _initialize_(self, *args, **kw):
        '''
        '''
        addr = self.address
        commands = [(addr, '1706', 100, 'stop motor, turn off amp'),
                    (addr, self._build_io(), 100, 'configure io pins'),
                    (addr, self._build_gains(), 100, 'set gains'),
                    (addr, '1701', 100, 'turn on amp'),
                    #                     (addr, '00', 100, 'reset position'),
                    #                     (addr, '1201', 100, 'set status')
                    #                   (addr, '0b', 100, 'clear bits')
        ]
        self._initialize_motor(commands, *args, **kw)

    #         self._execute_hex_commands(commands)
    #
    #         if self.home_at_startup:
    #             self._execute_hex_commands([(addr, '00', 100, 'reset position')])
    #             self._home_motor(*args, **kw)
    #         else:
    #             commands = [(addr, '1701', 100, 'turn on amp'), ]
    #             self._execute_hex_commands(commands)

    def _home_button_fired(self):
    #         self.progress = myProgressDialog(max=2)
    #         self.progress.open()
        self.home_status = 1
        self._home_motor()
        self.load_data_position()
        self.home_status = 2
        self.information_dialog('Homing Complete')
        self.home_status = 0

    def _home_motor(self, progress=None, *args, **kw):
        '''
        '''
        if progress is not None:
            progress.increase_max()
            progress.change_message('Homing {}'.format(self.name))

        addr = self.address

        cmd = '94'
        control = 'F6'

        v = self._float_to_hexstr(self.home_velocity)
        a = self._float_to_hexstr(self.home_acceleration)
        move_cmd = ''.join((cmd, control, v, a))

        #         home_control_byte = self._load_home_control_byte()
        #         home_cmd = '19{:02x}'.format(home_control_byte)

        cmds = [
            #                 (addr, home_cmd, 100, '=======Set Homing===='),
            (addr, move_cmd, 100, 'Send to Home')]
        self._execute_hex_commands(cmds)

        '''
            this is a hack. Because the home move is in velocity profile mode we cannot use the 0bit of the status byte to
            determine when the move is complete (0bit indicates when motor has reached requested velocity).

            instead we will poll the motor position until n successive positions are equal ie at a limt
        '''

        self.block(4, progress=progress, homing=True)

        # we are homed and should reset position

        cmds = [(addr, '00', 100, 'reset position')]

        self._execute_hex_commands(cmds)

    def reset_position(self, motor_off=True):
        '''
            1707 amp on
            1706 amp off
        '''
        addr = self.address
        b = '6' if motor_off else '7'
        cmds = [(addr, '170{}'.format(b), 100, 'Stop motor'),
                (addr, '00', 100, 'Reset Position')]
        self._execute_hex_commands(cmds)
        self._motor_position = 0

    def is_moving(self):
        return not self.enabled

    def progress_update(self, progress, signal):
        while not signal.is_set():
            pos = self._get_motor_position(verbose=False)
            if progress is not None:
                progress.change_message('{} position= {}'.format(self.name, pos),
                                        auto_increment=False)
                #                 do_after(25, progress.change_message, '{} position = {}'.format(self.name, pos))
            time.sleep(0.5)

    def block(self, n=3, tolerance=1, progress=None, homing=False):
        '''
        '''
        fail_cnt = 0
        pos_buffer = []

        while not self.parent.simulation:

            steps = self.load_data_position(set_pos=False)
            if homing:
                invoke_in_main_thread(self.trait_set, home_position=steps)

            if progress is not None:
                progress.change_message('{} position = {}'.format(self.name, steps),
                                        auto_increment=False)

            if steps is None:
                fail_cnt += 1
                if fail_cnt > 5:
                    break
                continue

            pos_buffer.append(steps)
            if len(pos_buffer) == n:
                if abs(float(sum(pos_buffer)) / n - steps) < tolerance:
                    break
                else:
                    pos_buffer.pop(0)

            time.sleep(0.1)

        if fail_cnt > 5:
            self.warning('Problem Communicating')

    def read_status(self, cb, verbose=True):
        if isinstance(cb, str):
            cb = '{:02x}'.format(int(cb, 2))

        addr = self.address
        cb = '13{}'.format(cb)
        cmd = self._build_command(addr, cb)
        status_byte = self.ask(cmd, is_hex=True,
                               delay=100,
                               nbytes=3,
                               verbose=verbose,
                               info='get status byte')
        return status_byte

    def read_defined_status(self, verbose=True):

        addr = self.address
        cmd = '0E'
        cmd = self._build_command(addr, cmd)
        status_byte = self.ask(cmd, is_hex=True,
                               delay=100,
                               nbytes=2,
                               info='get defined status',
                               verbose=verbose
        )
        return status_byte

    def load_data_position(self, set_pos=True):
        '''
        '''
        steps = self._get_motor_position(verbose=False)
        if steps is not None:
            pos = self.linear_mapper.map_data(steps)
            pos = max(self.min, min(self.max, pos))
            self.update_position = pos
            if set_pos:
                self._data_position = pos

            self.debug('Load data position {} {} steps= {}'.format(
                pos, self.units,
                steps,
            ))
            return steps

    def _moving(self, verbose=True):
        status_byte = self.read_defined_status(verbose=verbose)

        if status_byte in ('simulation', None):
            status_byte = 'DFDF'
        status_register = map(int, make_bitarray(int(status_byte[:2], 16)))
        return not status_register[7]

    def _get_motor_position(self, **kw):
        '''
        '''
        addr = self.address
        cmd = '13'
        control = '01'

        cmd = ''.join((cmd, control))
        cmd = (addr, cmd, 100, '')

        pos = self._execute_hex_command(cmd, nbytes=6, **kw)

        # trim off status and checksum bits
        if pos is not None:
            pos = pos[2:-2]
            pos = self._hexstr_to_float(pos)
            #            self._motor_position = pos
            return pos

    def _load_home_control_byte(self):
        '''
           control byte
                7 6 5 4 3 2 1 0
            97- 1 0 0 1 0 1 1 1
            0=capture home on limit1
            1=capture home on limit2
            2=turn motor off on home
            3=capture home on home
            4=stop abruptly
            5=stop smoothly
            6,7=not used- clear to 0
        '''

        return int('00010011', 2)

    def _load_trajectory_controlbyte(self):
        '''
           control byte
                7 6 5 4 3 2 1 0
            97- 1 0 0 1 0 1 1 1

            0=load pos
            1=load vel
            2=load acce
            3=load pwm
            4=enable servo
            5=profile mode 0=trap 1=vel
            6=direction trap mode 0=abs 1=rel vel mode 0=for. 1=back
            7=start motion now

        '''

        return '{:02x}'.format(int('10010111', 2))

    def _calculate_hysteresis_position(self, pos, hysteresis):
        hpos = pos + hysteresis
        #         self._hysteresis_correction = 0
        #         if hysteresis:
        hpos = max(self.min_steps, min(self.steps, hpos))
        #             if hpos > self.max:
        #                 self._hysteresis_correction = hpos - self.max
        #                 hpos = self.max
        #             elif hpos < self.min:
        #                 self._hysteresis_correction = hpos - self.min
        #                 hpos = self.min
        #             else:
        #                 self._hysteresis_correction = hysteresis
        return hpos

    def _set_motor_position_(self, pos, hysteresis=0, velocity=None):
        '''
        '''
        hpos = self._calculate_hysteresis_position(pos, hysteresis)
        self._desired_position = pos
        self._motor_position = hpos
        #        self._motor_position =npos= min(self.max, max(self.min, pos + hysteresis))
        #============pos is in mm===========
        addr = self.address
        cmd = 'D4'
        control = self._load_trajectory_controlbyte()
        position = self._float_to_hexstr(hpos)
        if velocity is None:
            velocity = self.velocity
        v = self._float_to_hexstr(velocity)

        a = self._float_to_hexstr(self.acceleration)
        #        print cmd, control, position, v, a
        cmd = ''.join((cmd, control, position, v, a))
        cmd = (addr, cmd, 100, 'setting motor steps {}'.format(hpos))

        self._execute_hex_command(cmd)

    def _update_position(self):
        '''
        '''

        if self._moving(verbose=False):
            self.enabled = False

        #        if not self._check_status_byte(0):
        #            self.enabled = False

        else:
        #             if self.use_hysteresis and \
            if self.hysteresis_value and \
                    not self.doing_hysteresis_correction and \
                    self.do_hysteresis:
            # move to original desired position at half velocity
            #                    print 'mp',self._motor_position, self.hysteresis_value
                self._set_motor_position_(
                    self._desired_position,
                    velocity=self.velocity / 2
                )
                self.doing_hysteresis_correction = True
            else:
                self.enabled = True
                if self.timer is not None:
                    self.timer.Stop()
                    #                     self.update_position = self._data_position
                    time.sleep(0.25)
                    self.load_data_position(set_pos=False)

        if not self.enabled:
            self.load_data_position(set_pos=False)

    def _get_data_position(self):
        '''
        '''
        return self._data_position

    def _set_data_position(self, pos):
        self.add_consumable((self._set_motor, pos))

    def _set_motor(self, pos, main=True):
    #         print self._data_position, pos
        if self._data_position != pos or not self._data_position:
            self.info('setting motor in data space {:0.3f}'.format(float(pos)))

            self._data_position = pos
            lm = self.linear_mapper
            steps = lm.map_steps(pos)

            hv = 0
            hysteresis = self.hysteresis_value
            if hysteresis:
                self.do_hysteresis = False
                if hysteresis < 0:
                    use_hysteresis = self._motor_position > steps
                else:
                    use_hysteresis = self._motor_position < steps

                if use_hysteresis:
                    self.do_hysteresis = True
                    self.doing_hysteresis_correction = False
                    hv = hysteresis

            self._set_motor_position_(steps, hv)

            def launch():
                self.timer = self.timer_factory()

            if main:
                invoke_in_main_thread(launch)
            else:
                launch()

            #            if self.parent.simulation:
            #                self.update_position = self._data_position

    def timer_factory(self):
        """
            reuse timer if possible
        """
        timer = self.timer

        func = self._update_position
        if timer is None:
            self._not_moving_count = 0
            timer = Timer(250, func)
        else:
            if timer.isActive():
                self.debug('reusing old timer')
            else:
                self._not_moving_count = 0
                timer = Timer(250, func)

        return timer

    def _float_to_hexstr(self, f, endianness='little'):
        '''
        '''
        f = max(0, f)
        fmt = '%sI' % ('<' if endianness == 'little' else '>')
        return binascii.hexlify(struct.pack(fmt, int(f)))

    def _hexstr_to_float(self, h, endianness='little'):
        '''
        '''
        fmt = '%sI' % ('<' if endianness == 'little' else '>')
        try:
            return struct.unpack(fmt, h.decode('hex'))[0]
        except Exception, e:
            print e