def get_serial_ports_list(self): """ Lists serial port names :raises EnvironmentError: On unsupported or unknown platforms :returns: A list of the serial ports available on the system """ if sys.platform.startswith('win'): ports = ['COM%s' % (i + 1) for i in range(256)] elif (sys.platform.startswith('linux') or sys.platform.startswith('cygwin')): # this excludes your current terminal "/dev/tty" ports = glob.glob('/dev/tty[A-Za-z]*') elif sys.platform.startswith('darwin'): ports = glob.glob('/dev/tty.*') else: raise EnvironmentError('Unsupported platform') result = [] for port in ports: try: if 'usbmodem' in port or 'COM' in port: s = serial.Serial(port) s.close() result.append(port) except Exception as e: log.debug( "Serial", 'Exception in testing port {}'.format(port)) log.debug("Serial", e) return result
def move_head(self, mode='absolute', **kwargs): self.set_coordinate_system(mode) current = self.get_head_position()['target'] log.debug('Motor Driver', 'Current Head Position: {}'.format(current)) target_point = { axis: kwargs.get( axis, 0 if mode == 'relative' else current[axis] ) for axis in 'xyz' } log.debug('Motor Driver', 'Destination: {}'.format(target_point)) time_interval = 0.5 # convert mm/min -> mm/sec, # multiply by time interval to get increment in mm increment = self.head_speed / 60 * time_interval vector_list = break_down_travel( current, Vector(target_point), mode=mode, increment=increment) # turn the vector list into axis args args_list = [] for vector in vector_list: flipped_vector = self.flip_coordinates(vector, mode) args_list.append( {axis.upper(): flipped_vector[axis] for axis in 'xyz' if axis in kwargs}) return self.consume_move_commands(args_list, increment)
def move(self, x=None, y=None, z=None, speed=None, absolute=True, **kwargs): if speed: code = self.MOVE else: code = self.RAPID_MOVE if absolute: self.send_command(self.ABSOLUTE_POSITIONING) else: self.send_command(self.RELATIVE_POSITIONING) args = {} """ Add x, y and z back to the kwargs. They're omitted when we name them as explicit keyword arguments, but it's much nicer to be able to pass them as anonymous parameters when calling this method. """ if x is not None: args['x'] = x if y is not None: args['y'] = y if z is not None: args['z'] = z for k in kwargs: args[k.upper()] = kwargs[k] log.debug("MotorDriver", "Moving: {}".format(args)) self.send_command(code, **args)
def wait_for_stat(self, stat=None): if self.DEBUG_ON: log.debug("Serial", "Turning on debug mode.") log.debug("Serial", "Awaiting stat responses.") self.send_command(self.DEBUG_ON) self._wait_for_stat = True if stat is not None: self._stat_command = stat
def plunge(self, volume): debug( "PipetteMotor", "Plunging {} axis ({}) to volume of {}µl." .format(self.axis, self.name, volume) ) depth = self.pipette.plunge_depth(volume) self.move(depth)
def process_command(self, command): parsed_command = self.parse_command(command) command_mapping = { 'G': self.process_nop, 'M': self.process_nop, 'G0': self.process_move_command, 'G4': self.process_dwell_command, 'M114': self.process_get_position, 'G92': self.process_set_position_command, 'G28': self.process_home_command, 'M119': self.process_get_endstops, 'M999': self.process_calm_down, 'M63': self.process_disengage_feedback, 'G90': self.process_absolute_positioning, 'G91': self.process_relative_positioning, 'M40': self.process_mosfet_state, 'M41': self.process_mosfet_state, 'M42': self.process_mosfet_state, 'M43': self.process_mosfet_state, 'M44': self.process_mosfet_state, 'M45': self.process_mosfet_state, 'M46': self.process_mosfet_state, 'M47': self.process_mosfet_state, 'M48': self.process_mosfet_state, 'M49': self.process_mosfet_state, 'M50': self.process_mosfet_state, 'M51': self.process_mosfet_state, 'M17': self.process_power_on, 'M18': self.process_power_off, 'version': self.process_version, 'config-get': self.process_config_get, 'config-set': self.process_config_set } if parsed_command: command = parsed_command['command'] arguments = parsed_command['arguments'] if command in command_mapping: command_func = command_mapping[command] log.debug( 'Virtual Smoothie', 'Processing {} calling {}'.format( parsed_command, command_func.__name__)) message = command_func(arguments) self.insert_response(message) else: log.error( 'Virtual Smoothie', 'Command {} is not supported'.format(command))
def _run(self, index): cur = self._commands[index] command = list(cur)[0] kwargs = cur[command] self._run_in_context_handler(command, **kwargs) for h in self._handlers: debug( "Protocol", "{}.{}: {}" .format(type(h).__name__, command, kwargs) ) h.before_each() method = getattr(h, command) method(**kwargs) h.after_each()
def write_to_serial(self, data, max_tries=10, try_interval=0.2): log.debug("Serial", "Write: {}".format(str(data).encode())) if self.connection is None: log.warn("Serial", "No connection found.") return if self.is_connected(): self.connection.write(str(data).encode()) return self.wait_for_response() elif max_tries > 0: self.reset_port() return self.write_to_serial( data, max_tries=max_tries - 1, try_interval=try_interval ) else: log.error("Serial", "Cannot connect to serial port.") return b''
def readline_from_serial(self): msg = b'' if self.is_connected(): # serial.readline() returns an empty byte string if it times out msg = self.connection.readline().strip() if msg: log.debug("Serial", "Read: {}".format(msg)) # detect if it hit a home switch if b'!!' in msg or b'limit' in msg: # TODO (andy): allow this to bubble up so UI is notified log.debug('Serial', 'home switch hit') self.flush_port() self.calm_down() raise RuntimeWarning('limit switch hit') return msg
def get_position(self): res = self.send_command(self.GET_POSITION) # remove the "ok " from beginning of response res = res.decode('utf-8')[3:] coords = {} try: response_dict = json.loads(res).get(self.GET_POSITION) coords = {'target': {}, 'current': {}} for letter in 'xyzab': # the lowercase axis are the "real-time" values coords['current'][letter] = response_dict.get(letter, 0) # the uppercase axis are the "target" values coords['target'][letter] = response_dict.get(letter.upper(), 0) except JSON_ERROR: log.debug("Serial", "Error parsing JSON string:") log.debug("Serial", res) return coords
def wait_for_response(self, timeout=20.0): count = 0 max_retries = int(timeout / self.serial_timeout) while count < max_retries: count = count + 1 out = self.readline_from_serial() if out: log.debug( "Serial", "Waited {} lines for response {}.".format(count, out) ) return out else: if count == 1 or count % 10 == 0: # Don't log all the time; gets spammy. log.debug( "Serial", "Waiting {} lines for response.".format(count) ) raise RuntimeWarning('no response after {} seconds'.format(timeout))
def consume_move_commands(self, args_list, step): tolerance = step * 0.5 self.current_commands = list(args_list) while self.can_move.wait(): if self.stopped.is_set(): self.resume() return (False, self.STOPPED) if self.current_commands: args = self.current_commands.pop(0) else: self.wait_for_arrival() break self.wait_for_arrival(tolerance) log.debug("MotorDriver", "Moving head: {}".format(args)) res = self.send_command(self.MOVE, **args) if res != b'ok': return (False, self.SMOOTHIE_ERROR) return (True, self.SMOOTHIE_SUCCESS)
def connect_to_physical_smoothie(self, port, options=None): try: self.connection = serial.Serial( port=port, baudrate=115200, timeout=self.serial_timeout ) # sometimes pyserial swallows the initial b"Smoothie\r\nok\r\n" # so just always swallow it ourselves self.reset_port() log.debug("Serial", "Connected to {}".format(port)) return self.calm_down() except serial.SerialException as e: log.debug( "Serial", "Error connecting to {}".format(port)) log.error("Serial", e) return False
def write_to_serial(self, data, max_tries=10, try_interval=0.2): log.debug("Serial", "Write: {}".format(str(data).encode())) if self.connection is None: log.warn("Serial", "No connection found.") return if self.connection.isOpen(): self.connection.write(str(data).encode()) if self._wait_for_stat is True and self._stat_command: waiting = True count = 0 while waiting: count = count + 1 out = self.connection.readline().decode().strip() log.debug("Serial", "Read: {}".format(out)) if out == self._stat_command: waiting = False log.debug( "Serial", "Waited {} lines for stat.".format(count) ) else: if count == 1 or count % 10 == 0: # Don't log all the time; gets spammy. log.debug( "Serial", "Waiting {} lines for stat ({})." .format(count, self._stat_command) ) else: out = self.connection.readline() log.debug("Serial", "Read: {}".format(out)) return out elif max_tries > 0: time.sleep(try_interval) self.write_to_serial( data, max_tries=max_tries - 1, try_interval=try_interval ) else: log.error("Serial", "Cannot connect to serial port.")
def test_calibrate_placeable(self): well = self.plate[0] pos = well.from_center(x=0, y=0, z=0, reference=self.plate) location = (self.plate, pos) well_deck_coordinates = well.center(well.get_deck()) dest = well_deck_coordinates + Vector(1, 2, 3) log.debug('Unit test', 'Destination: {}'.format(well_deck_coordinates)) self.robot._driver.move_head(x=dest['x'], y=dest['y'], z=dest['z']) self.p200.calibrate_position(location) expected_calibration_data = { 'A2': { 'children': { '96-flat': { 'delta': (1.0, 2.0, 3.0) }}}} self.assertDictEqual( self.p200.calibration_data, expected_calibration_data)
def move_motors(self, **kwargs): debug("MotorHandler", "Moving: {}".format(kwargs)) self._driver.move(**kwargs)
def droptip(self): debug( "PipetteMotor", "Droptip on {} axis ({}).".format(self.axis, self.name) ) self.move(self.pipette.droptip)
def blowout(self): debug( "PipetteMotor", "Blowout on {} axis ({}).".format(self.axis, self.name) ) self.move(self.pipette.blowout)
def reset(self): debug( "PipetteMotor", "Resetting {} axis ({}).".format(self.axis, self.name) ) self.move(0)
def connect(self, device=None, port=None): self.connection = serial.Serial(port=device or port) self.connection.close() self.connection.open() log.debug("Serial", "Connected to {}".format(device or port)) self.wait_for_stat()
def write(self, data): if self.isOpen() is False: raise IOError("Connection not open.") log.debug('GCodeLogger', 'Writing: {}'.format(data)) self.write_buffer.append(data)