def move(self, pos, absolute=True): # Handle units as follows: # 1. Treat raw numbers as encoder counts. # 2. If units are provided (as a Quantity), check if they're encoder # counts. If they aren't, apply scale factor. if not isinstance(pos, pq.Quantity): pos_ec = int(pos) else: if pos.units == pq.counts: pos_ec = int(pos.magnitude) else: scaled_pos = (pos * self.scale_factors[0]) # Force a unit error. try: pos_ec = int(scaled_pos.rescale(pq.counts).magnitude) except: raise ValueError( "Provided units are not compatible with current motor scale factor." ) # Now that we have our position as an integer number of encoder # counts, we're good to move. pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.MOT_MOVE_ABSOLUTE if absolute else _cmds.ThorLabsCommands.MOT_MOVE_RELATIVE, param1=None, param2=None, dest=self._apt._dest, source=0x01, data=struct.pack('<Hl', self._idx_chan, pos_ec)) response = self._apt.querypacket( pkt, expect=_cmds.ThorLabsCommands.MOT_MOVE_COMPLETED)
def __init__(self, filelike): super(ThorLabsAPT, self).__init__(filelike) self._dest = 0x50 # Generic USB device; make this configurable later. # Provide defaults in case an exception occurs below. self._serial_number = None self._model_number = None self._hw_type = None self._fw_version = None self._notes = "" self._hw_version = None self._mod_state = None self._n_channels = 0 self._channel = () # Perform a HW_REQ_INFO to figure out the model number, serial number, # etc. try: req_packet = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.HW_REQ_INFO, param1=0x00, param2=0x00, dest=self._dest, source=0x01, data=None) hw_info = self.querypacket( req_packet, expect=_cmds.ThorLabsCommands.HW_GET_INFO) self._serial_number = str(hw_info._data[0:4]).encode('hex') self._model_number = str(hw_info._data[4:12]).replace('\x00', '').strip() hw_type_int = struct.unpack('<H', str(hw_info._data[12:14]))[0] if hw_type_int == 45: self._hw_type = 'Multi-channel controller motherboard' elif hw_type_int == 44: self._hw_type = 'Brushless DC controller' else: self._hw_type = 'Unknown type: {}'.format(hw_type_int) # Note that the fourth byte is padding, so we strip out the first # three bytes and format them. self._fw_version = "{0[0]}.{0[1]}.{0[2]}".format( str(hw_info._data[14:18]).encode('hex')) self._notes = str(hw_info._data[18:66]).replace('\x00', '').strip() self._hw_version = struct.unpack('<H', str(hw_info._data[78:80]))[0] self._mod_state = struct.unpack('<H', str(hw_info._data[80:82]))[0] self._n_channels = struct.unpack('<H', str(hw_info._data[82:84]))[0] except Exception as e: logger.error( "Exception occured while fetching hardware info: {}".format(e)) # Create a tuple of channels of length _n_channel_type if self._n_channels > 0: self._channel = list( self._channel_type(self, chan_idx) for chan_idx in xrange(self._n_channels))
def enabled(self, newval): pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.MOD_SET_CHANENABLESTATE, param1=self._idx_chan, param2=0x01 if newval else 0x02, dest=self._apt._dest, source=0x01, data=None) self._apt.sendpacket(pkt)
def output_position(self, pos): pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.PZ_SET_OUTPUTPOS, param1=None, param2=None, dest=self._apt._dest, source=0x01, data=struct.pack('<HH', self._idx_chan, pos)) self._apt.sendpacket(pkt)
def go_home(self): pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.MOT_MOVE_HOME, param1=self._idx_chan, param2=0x00, dest=self._apt._dest, source=0x01, data=None) self._apt.sendpacket(pkt)
def change_position_control_mode(self, closed, smooth=True): mode = 1 + (int(closed) | int(smooth) << 1) pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.PZ_SET_POSCONTROLMODE, param1=self._idx_chan, param2=mode, dest=self._apt._dest, source=0x01, data=None) self._apt.sendpacket(pkt)
def enabled(self): pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.MOD_REQ_CHANENABLESTATE, param1=self._idx_chan, param2=0x00, dest=self._apt._dest, source=0x01, data=None) resp = self._apt.querypacket( pkt, expect=_cmds.ThorLabsCommands.MOD_GET_CHANENABLESTATE) return not bool(resp._param2 - 1)
def is_position_control_closed(self): pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.PZ_REQ_POSCONTROLMODE, param1=self._idx_chan, param2=0x00, dest=self._apt._dest, source=0x01, data=None) resp = self._apt.querypacket( pkt, expect=_cmds.ThorLabsCommands.PZ_GET_POSCONTROLMODE) return bool((resp._param2 - 1) & 1)
def position_encoder(self): pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.MOT_REQ_ENCCOUNTER, param1=self._idx_chan, param2=0x00, dest=self._apt._dest, source=0x01, data=None) response = self._apt.querypacket( pkt, expect=_cmds.ThorLabsCommands.MOT_GET_ENCCOUNTER) chan, pos = struct.unpack('<Hl', response._data) return pq.Quantity(pos, 'counts')
def output_position(self): pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.PZ_REQ_OUTPUTPOS, param1=self._idx_chan, param2=0x00, dest=self._apt._dest, source=0x01, data=None) resp = self._apt.querypacket( pkt, expect=_cmds.ThorLabsCommands.PZ_GET_OUTPUTPOS) chan, pos = struct.unpack('<HH', resp._data) return pos
def identify(self): ''' Causes a light on the APT instrument to blink, so that it can be identified. ''' pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.MOD_IDENTIFY, param1=0x00, param2=0x00, dest=self._dest, source=0x01, data=None) self.sendpacket(pkt)
def led_intensity(self, intensity): ''' The output intensity of the LED display. :param float intensity: Ranges from 0 to 1. ''' pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.PZ_SET_TPZ_DISPSETTINGS, param1=None, param2=None, dest=self._dest, source=0x01, data=struct.pack('<H', int(round(255 * intensity)))) self.sendpacket(pkt)
def led_intensity(self): ''' The output intensity of the LED display. :type: `float` between 0 and 1. ''' pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.PZ_REQ_TPZ_DISPSETTINGS, param1=0x01, param2=0x00, dest=self._dest, source=0x01, data=None) resp = self.querypacket(pkt) return float(struct.unpack('<H', resp._data)[0]) / 255
def max_travel(self): pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.PZ_REQ_MAXTRAVEL, param1=self._idx_chan, param2=0x00, dest=self._apt._dest, source=0x01, data=None) resp = self._apt.querypacket(pkt) # Not all APT piezo devices support querying the maximum travel # distance. Those that do not simply ignore the PZ_REQ_MAXTRAVEL # packet, so that the response is empty. if resp is None: return NotImplemented chan, int_maxtrav = struct.unpack('<HH', resp._data) return int_maxtrav * pq.Quantity(100, 'nm')
def status_bits(self): # NOTE: the difference between MOT_REQ_STATUSUPDATE and MOT_REQ_DCSTATUSUPDATE confuses me pkt = _packets.ThorLabsPacket( message_id=_cmds.ThorLabsCommands.MOT_REQ_STATUSUPDATE, param1=self._idx_chan, param2=0x00, dest=self._apt._dest, source=0x01, data=None) # The documentation claims there are 14 data bytes, but it seems there are sometimes # some extra random ones... resp_data = self._apt.querypacket(pkt)._data[:14] ch_ident, position, enc_count, status_bits = struct.unpack( '<HLLL', resp_data) status_dict = dict( (key, (status_bits & bit_mask > 0)) for key, bit_mask in self.__STATUS_BIT_MASK.iteritems()) return status_dict