Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): Add G Code for pipette config in driver #3388

Merged
merged 21 commits into from
May 2, 2019
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions api/src/opentrons/config/robot_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,14 @@
'C': C_ACCELERATION
}

DEFAULT_STEPS_PER_MM = 'M92 X80.00 Y80.00 Z400 A400 B768 C768'
DEFAULT_STEPS_PER_MM: Dict[str, float] = {
'X': 80.00,
'Y': 80.00,
'Z': 400,
'A': 400,
'B': 768,
'C': 768
}
# This probe height is ~73 from deck to the top surface of the switch body
# per CAD; 74.3mm is the nominal for engagement from the switch drawing.
# Note that this has a piece-to-piece tolerance stackup of +-1.5mm
Expand Down Expand Up @@ -242,9 +249,10 @@ def _build_tip_probe(tip_probe_settings: dict) -> tip_probe_config:
)


def _build_acceleration(from_conf: Union[Dict, str, None]) -> Dict[str, float]:
def _build_conf_dict(
from_conf: Union[Dict, str, None], default) -> Dict[str, float]:
if not from_conf or isinstance(from_conf, str):
return DEFAULT_ACCELERATION
return default
else:
return from_conf

Expand All @@ -254,8 +262,10 @@ def _build_config(deck_cal: List[List[float]],
cfg = robot_config(
name=robot_settings.get('name', 'Ada Lovelace'),
version=int(robot_settings.get('version', ROBOT_CONFIG_VERSION)),
steps_per_mm=robot_settings.get('steps_per_mm', DEFAULT_STEPS_PER_MM),
acceleration=_build_acceleration(robot_settings.get('acceleration')),
steps_per_mm=_build_conf_dict(
robot_settings.get('steps_per_mm'), DEFAULT_STEPS_PER_MM),
acceleration=_build_conf_dict(
robot_settings.get('acceleration'), DEFAULT_ACCELERATION),
gantry_calibration=deck_cal or DEFAULT_DECK_CALIBRATION,
instrument_offset=build_fallback_instrument_offset(robot_settings),
tip_length=robot_settings.get('tip_length', DEFAULT_TIP_LENGTH_DICT),
Expand Down
39 changes: 39 additions & 0 deletions api/src/opentrons/drivers/smoothie_drivers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,42 @@ class VirtualSmoothie(object):

def __init__(self):
pass


class SimulatingDriver:
def __init__(self):
self._steps_per_mm = {}

def home(self):
pass

def update_pipette_config(self, axis, data):
'''
Updates the following configs for a given pipette mount based on
the detected pipette type:
- homing positions M365.0
- Max Travel M365.1
- endstop debounce M365.2 (NOT for zprobe debounce)
- retract from endstop distance M365.3
'''
pass

@property
def current(self):
return self._current_settings

@property
def speed(self):
pass

@property
def steps_per_mm(self):
return self._steps_per_mm

@steps_per_mm.setter
def steps_per_mm(self, axis, mm):
# Keep track of any updates to the steps per mm per axis
self._steps_per_mm[axis] = mm

def update_steps_per_mm(self, data):
pass
67 changes: 65 additions & 2 deletions api/src/opentrons/drivers/smoothie_drivers/driver_3_0.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
'PUSH_SPEED': 'M120',
'POP_SPEED': 'M121',
'SET_SPEED': 'G0F',
'STEPS_PER_MM': 'M92',
'READ_INSTRUMENT_ID': 'M369',
'WRITE_INSTRUMENT_ID': 'M370',
'READ_INSTRUMENT_MODEL': 'M371',
Expand Down Expand Up @@ -301,7 +302,7 @@ def __init__(self, config):
self._saved_max_speed_settings = self._max_speed_settings.copy()
self._combined_speed = float(DEFAULT_AXES_SPEED)
self._saved_axes_speed = float(self._combined_speed)

self._steps_per_mm = {}
self._acceleration = config.acceleration.copy()
self._saved_acceleration = config.acceleration.copy()

Expand Down Expand Up @@ -431,6 +432,45 @@ def write_pipette_model(self, mount, data_string):
self._write_to_pipette(
GCODES['WRITE_INSTRUMENT_MODEL'], mount, data_string)

def update_pipette_config(self, axis, data):
'''
Updates the following configs for a given pipette mount based on
the detected pipette type:
- homing positions M365.0
- Max Travel M365.1
- endstop debounce M365.2 (NOT for zprobe debounce)
- retract from endstop distance M365.3
'''
gcodes = {
'retract': 'M365.3',
'debounce': 'M365.2',
'max_travel': 'M365.1',
'home': 'M365.0'}

res_msg = f'The following configs were updated for {axis}: '

def _parse_command(result):
# ensure smoothie received code and changed value through
# return message. Format of return message:
# <Axis> (or E for endstop) updated <Value>
nonlocal res_msg
arr_result = result.strip().split(' ')
res_msg = res_msg + arr_result[0] + ',' + arr_result[2]

for key, value in data.items():
if key == 'debounce':
# debounce variable for all axes, so do not specify an axis
cmd = f' O{value}'
else:
cmd = f' {axis}{value}'
res = self._send_command(gcodes[key] + cmd)
if res is None:
raise ValueError(
f'{key} was not updated to {value} on {axis} axis')
_parse_command(res)

return res_msg

# FIXME (JG 9/28/17): Should have a more thought out
# way of simulating vs really running
def connect(self, port=None):
Expand Down Expand Up @@ -565,6 +605,15 @@ def current(self):
def speed(self):
pass

@property
def steps_per_mm(self):
return self._steps_per_mm

@steps_per_mm.setter
def steps_per_mm(self, axis, mm):
# Keep track of any updates to the steps per mm per axis
self._steps_per_mm[axis] = mm

def set_speed(self, value):
''' set total axes movement speed in mm/second'''
self._combined_speed = float(value)
Expand Down Expand Up @@ -984,14 +1033,28 @@ def _setup(self):
# use gpio to reset into a known state
self._smoothie_reset()
self._reset_from_error()
self._send_command(self._config.steps_per_mm)
self.update_steps_per_mm(self._config.steps_per_mm)
self._send_command(GCODES['ABSOLUTE_COORDS'])
self._save_current(self.current, axes_active=False)
self.update_position(default=self.homed_position)
self.pop_axis_max_speed()
self.pop_speed()
self.pop_acceleration()

def update_steps_per_mm(self, data):
# Using M92, update steps per mm for a given axis
if isinstance(data, str):
# Unfortunately update server calls driver._setup() before the
# update can correctly load the robot_config change on disk.
# Need to account for old command format to avoid this issue.
self._send_command(data)
else:
cmd = ''
for axis, value in data.items():
cmd = f'{cmd} {axis}{value}'
self.steps_per_mm[axis] = value
self._send_command(GCODES['STEPS_PER_MM'] + cmd)

def _read_from_pipette(self, gcode, mount) -> Optional[str]:
'''
Read from an attached pipette's internal memory. The gcode used
Expand Down
10 changes: 10 additions & 0 deletions api/src/opentrons/hardware_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,16 @@ async def cache_instruments(self,
self._config.instrument_offset[mount.name.lower()],
instrument_data['id'])
self._attached_instruments[mount] = p
if 'v2' in model:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if you replace a v2 with a v1.x? Don't we need to reset the steps per mm?

# Check if new model of pipettes, load smoothie configs
# for this particular model
axis = 'B' if mount == 'left' else 'C'
Laura-Danielle marked this conversation as resolved.
Show resolved Hide resolved
self._backend._smoothie_driver.update_steps_per_mm(
{axis: 2133.33})
# TODO(LC25-4-2019): Modify configs to update to as
# testing informs better values
self._backend._smoothie_driver.update_pipette_config(
axis, {'home': 172.15})
else:
self._attached_instruments[mount] = None
mod_log.info("Instruments found: {}".format(
Expand Down
4 changes: 4 additions & 0 deletions api/src/opentrons/hardware_control/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from contextlib import contextmanager
from opentrons import types
from opentrons.config.pipette_config import config_models
from opentrons.drivers.smoothie_drivers import SimulatingDriver
from . import modules


Expand Down Expand Up @@ -81,6 +82,9 @@ def __init__(
self._position = copy.copy(_HOME_POSITION)
# Engaged axes start all true in smoothie for some reason so we
# imitate that here
# TODO(LC2642019) Create a simulating driver for smoothie instead of
# using a flag
self._smoothie_driver = SimulatingDriver()
self._engaged_axes = {ax: True for ax in _HOME_POSITION}
self._lights = {'button': False, 'rails': False}
self._run_flag = Event()
Expand Down
16 changes: 15 additions & 1 deletion api/src/opentrons/legacy_api/robot/robot.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from opentrons.drivers.smoothie_drivers import driver_3_0
from opentrons.trackers import pose_tracker
from opentrons.config import feature_flags as fflags
from opentrons.config.robot_configs import load
from opentrons.config.robot_configs import load, DEFAULT_STEPS_PER_MM
from opentrons.legacy_api import containers, modules
from opentrons.legacy_api.containers import Container, load_new_labware,\
save_new_offsets
Expand Down Expand Up @@ -271,6 +271,20 @@ def cache_instrument_models(self):
log.debug("Updating instrument model cache")
for mount in self.model_by_mount.keys():
model_value = self._driver.read_pipette_model(mount)
axis = 'B' if mount == 'left' else 'C'
if model_value and 'v2' in model_value:
# Check if new model of pipettes, load smoothie configs
# for this particular model
self._driver.update_steps_per_mm({axis: 2133.33})
# TODO(LC25-4-2019): Modify configs to update to as
# testing informs better values
self._driver.update_pipette_config(axis, {'home': 172.15})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we need to update the pipette config back to defaults for non-v2 models (or take the time now to add this data to the model configs)

else:
if self._driver.steps_per_mm.get(axis) \
!= DEFAULT_STEPS_PER_MM[axis]:
self._driver.update_steps_per_mm(
{axis: DEFAULT_STEPS_PER_MM[axis]})

if model_value:
id_response = self._driver.read_pipette_id(mount)
else:
Expand Down
Loading