From 85b3d6bca3a3faca9c35eca557373b6f8594cec6 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Mon, 4 Jan 2021 16:13:20 -0500 Subject: [PATCH] fix(api): bump y max bounds to match robot geometry (#7140) We now helpfully check that jogs don't overlap the maximum motion range of each axis, at least in the positive direction, using the home positions. This works pretty well for Z and X since it straightforwardly prevents hard limit errors from unintentional endstop triggers, which happen at the home position as one might expect. Unfortunately, the robot geometry makes this approach completely invalid for Y. When homing, the Y endstop (which is located on the back of the head) actually interacts with a post projecting from the back of the robot, which is located on the right side of the robot. This is the reason we must always home X before homing Y: to make sure the head is in the correct position for the Y endstop to interact with the post. If the head is _not_ all the way to the right, it can actually go much farther back - past the home location, past where the switch would interact with the post. And the deck layout is designed to take advantage of this; the position that A1 of most labware lives in is farther back than the post, and thus customers would get spurious jog failures when calibrating labwares in slots 10 and 11 using single channel pipettes (because multi channels are wider in Y, the gantry doesn't go far enough back to trigger the issue). The fix is to create a different bound in the Y. Closes #6886 --- .../drivers/smoothie_drivers/__init__.py | 25 ++++++++++++++++++- .../drivers/smoothie_drivers/driver_3_0.py | 24 ++++++++---------- .../opentrons/hardware_control/controller.py | 2 +- .../opentrons/hardware_control/simulator.py | 22 ++++++++-------- api/src/opentrons/tools/overnight_test.py | 4 +-- 5 files changed, 48 insertions(+), 29 deletions(-) diff --git a/api/src/opentrons/drivers/smoothie_drivers/__init__.py b/api/src/opentrons/drivers/smoothie_drivers/__init__.py index cf2eccd42bd..ae360338af2 100644 --- a/api/src/opentrons/drivers/smoothie_drivers/__init__.py +++ b/api/src/opentrons/drivers/smoothie_drivers/__init__.py @@ -1,3 +1,17 @@ +from typing_extensions import Final + + +HOMED_POSITION: Final = { + 'X': 418.0, + 'Y': 353.0, + 'Z': 218.0, + 'A': 218.0, + 'B': 19.0, + 'C': 19.0 +} +Y_BOUND_OVERRIDE: Final = 370 + + class SmoothieDriver(object): def __init__(self): @@ -13,7 +27,6 @@ def __init__(self): class SimulatingDriver: def __init__(self): self._steps_per_mm = {} - self.homed_position = {} def home(self, axis): pass @@ -81,3 +94,13 @@ def set_dwelling_current(self, settings): def set_acceleration(self, settings): pass + + @property + def homed_position(self): + return HOMED_POSITION.copy() + + @property + def axis_bounds(self): + position = HOMED_POSITION.copy() + position['Y'] = Y_BOUND_OVERRIDE + return position diff --git a/api/src/opentrons/drivers/smoothie_drivers/driver_3_0.py b/api/src/opentrons/drivers/smoothie_drivers/driver_3_0.py index c88444f00cb..453b9b0f3ab 100755 --- a/api/src/opentrons/drivers/smoothie_drivers/driver_3_0.py +++ b/api/src/opentrons/drivers/smoothie_drivers/driver_3_0.py @@ -5,6 +5,7 @@ from time import sleep, time from threading import Event, RLock from typing import Any, Dict, Optional, Union, List, Tuple, cast + from math import isclose from serial.serialutil import SerialException # type: ignore @@ -17,6 +18,9 @@ from opentrons.drivers.rpi_drivers.gpio_simulator import SimulatingGPIOCharDev from opentrons.drivers.rpi_drivers.dev_types import GPIODriverLike from opentrons.system import smoothie_update +from . import HOMED_POSITION, Y_BOUND_OVERRIDE + + """ - Driver is responsible for providing an interface for motion control - Driver is the only system component that knows about GCODES or how smoothie @@ -31,18 +35,6 @@ ERROR_KEYWORD = 'error' ALARM_KEYWORD = 'alarm' - -# TODO (artyom, ben 20171026): move to config -HOMED_POSITION: Dict[str, float] = { - 'X': 418, - 'Y': 353, - 'Z': 218, - 'A': 218, - 'B': 19, - 'C': 19 -} - - PLUNGER_BACKLASH_MM = 0.3 LOW_CURRENT_Z_SPEED = 30 CURRENT_CHANGE_DELAY = 0.005 @@ -415,9 +407,15 @@ def gpio_chardev(self, gpio_chardev): self._gpio_chardev = gpio_chardev @property - def homed_position(self): + def homed_position(self) -> Dict[str, float]: return self._homed_position.copy() + @property + def axis_bounds(self) -> Dict[str, float]: + bounds = {k: v for k, v in self._homed_position.items()} + bounds['Y'] = Y_BOUND_OVERRIDE + return bounds + def _update_position(self, target): self._position.update({ axis: value diff --git a/api/src/opentrons/hardware_control/controller.py b/api/src/opentrons/hardware_control/controller.py index 80d3c01480e..63ba9bf098b 100644 --- a/api/src/opentrons/hardware_control/controller.py +++ b/api/src/opentrons/hardware_control/controller.py @@ -263,7 +263,7 @@ async def connect(self, port: str = None): def axis_bounds(self) -> Dict[Axis, Tuple[float, float]]: """ The (minimum, maximum) bounds for each axis. """ return {Axis[ax]: (0, pos) for ax, pos - in self._smoothie_driver.homed_position.items() + in self._smoothie_driver.axis_bounds.items() if ax not in 'BC'} @property diff --git a/api/src/opentrons/hardware_control/simulator.py b/api/src/opentrons/hardware_control/simulator.py index 81e2dd7a5cd..522c1730634 100644 --- a/api/src/opentrons/hardware_control/simulator.py +++ b/api/src/opentrons/hardware_control/simulator.py @@ -5,7 +5,6 @@ from threading import Event from typing import (Dict, Optional, List, Tuple, TYPE_CHECKING, Sequence) -from typing_extensions import Final from contextlib import contextmanager from opentrons_shared_data.pipette import dummy_model_for_name @@ -17,6 +16,7 @@ load) from opentrons.config.types import RobotConfig from opentrons.drivers.smoothie_drivers import SimulatingDriver + from opentrons.drivers.rpi_drivers.gpio_simulator import SimulatingGPIOCharDev from . import modules @@ -36,10 +36,6 @@ MODULE_LOG = logging.getLogger(__name__) -_HOME_POSITION = {'X': 418.0, 'Y': 353.0, 'Z': 218.0, - 'A': 218.0, 'B': 19.0, 'C': 19.0} - - class Simulator: """ This is a subclass of hardware_control that only simulates the hardware actions. It is suitable for use on a dev machine or on @@ -116,7 +112,8 @@ def __init__( """ self.config = config self._loop = loop - self._gpio_chardev: Final = gpio_chardev + self._smoothie_driver = SimulatingDriver() + self._gpio_chardev = gpio_chardev def _sanitize_attached_instrument( passed_ai: Dict[str, Optional[str]] = None)\ @@ -139,13 +136,13 @@ def _sanitize_attached_instrument( m: _sanitize_attached_instrument(attached_instruments.get(m)) for m in types.Mount} self._stubbed_attached_modules = attached_modules - self._position = copy.copy(_HOME_POSITION) + self._position = copy.copy(self._smoothie_driver.homed_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._engaged_axes = {ax: True for ax in self._smoothie_driver.homed_position} self._lights = {'button': False, 'rails': False} self._run_flag = Event() self._run_flag.set() @@ -169,7 +166,7 @@ def move(self, target_position: Dict[str, float], def home(self, axes: List[str] = None) -> Dict[str, float]: # driver_3_0-> HOMED_POSITION checked_axes = axes or 'XYZABC' - self._position.update({ax: _HOME_POSITION[ax] + self._position.update({ax: self._smoothie_driver.homed_position[ax] for ax in checked_axes}) self._engaged_axes.update({ax: True for ax in checked_axes}) @@ -178,7 +175,7 @@ def home(self, axes: List[str] = None) -> Dict[str, float]: def fast_home( self, axis: Sequence[str], margin: float) -> Dict[str, float]: for ax in axis: - self._position[ax] = _HOME_POSITION[ax] + self._position[ax] = self._smoothie_driver.homed_position[ax] self._engaged_axes[ax] = True return self._position @@ -288,7 +285,8 @@ async def build_module( @property def axis_bounds(self) -> Dict[Axis, Tuple[float, float]]: """ The (minimum, maximum) bounds for each axis. """ - return {Axis[ax]: (0, pos) for ax, pos in _HOME_POSITION.items() + return {Axis[ax]: (0, pos) for ax, pos + in self._smoothie_driver.axis_bounds.items() if ax not in 'BC'} @property diff --git a/api/src/opentrons/tools/overnight_test.py b/api/src/opentrons/tools/overnight_test.py index c1a93beadf9..5efd4902393 100644 --- a/api/src/opentrons/tools/overnight_test.py +++ b/api/src/opentrons/tools/overnight_test.py @@ -29,8 +29,8 @@ attempts_to_home = 1 too_many_attempts_to_home = 3 -XY_TOLERANCE = 30 -ZA_TOLERANCE = 10 +XY_TOLERANCE = 30.0 +ZA_TOLERANCE = 10.0 AXIS_TEST_SKIPPING_TOLERANCE = 0.5