From ce1f3609f1f1493be9eca6818d62e0d8412170a5 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Thu, 17 Dec 2020 10:56:44 -0500 Subject: [PATCH 1/2] fix(api): bump y max bounds to match robot geometry 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/driver_3_0.py | 26 +++++++++++++------ .../opentrons/hardware_control/controller.py | 2 +- .../opentrons/hardware_control/simulator.py | 7 +++-- api/src/opentrons/tools/overnight_test.py | 4 +-- 4 files changed, 26 insertions(+), 13 deletions(-) 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..540efbbde31 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,9 @@ from time import sleep, time from threading import Event, RLock from typing import Any, Dict, Optional, Union, List, Tuple, cast +from typing import Any, Dict, Optional, Union, List, Tuple, cast +from typing_extensions import Final + from math import isclose from serial.serialutil import SerialException # type: ignore @@ -33,14 +36,15 @@ # TODO (artyom, ben 20171026): move to config -HOMED_POSITION: Dict[str, float] = { - 'X': 418, - 'Y': 353, - 'Z': 218, - 'A': 218, - 'B': 19, - 'C': 19 +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 PLUNGER_BACKLASH_MM = 0.3 @@ -415,9 +419,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..a021a5f9510 100644 --- a/api/src/opentrons/hardware_control/simulator.py +++ b/api/src/opentrons/hardware_control/simulator.py @@ -36,7 +36,10 @@ MODULE_LOG = logging.getLogger(__name__) -_HOME_POSITION = {'X': 418.0, 'Y': 353.0, 'Z': 218.0, +_HOME_POSITION: Final = {'X': 418.0, 'Y': 353.0, 'Z': 218.0, + 'A': 218.0, 'B': 19.0, 'C': 19.0} + +_BOUNDS: Final = {'X': 418.0, 'Y': 370.0, 'Z': 218.0, 'A': 218.0, 'B': 19.0, 'C': 19.0} @@ -288,7 +291,7 @@ 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 _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 From b4721eab232e9b8c84dc466881cd9c6081b6a931 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Thu, 17 Dec 2020 12:47:13 -0500 Subject: [PATCH 2/2] make some const definitions more cohesive --- .../drivers/smoothie_drivers/__init__.py | 25 ++++++++++++++++++- .../drivers/smoothie_drivers/driver_3_0.py | 18 +++---------- .../opentrons/hardware_control/simulator.py | 25 ++++++++----------- 3 files changed, 37 insertions(+), 31 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 540efbbde31..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,8 +5,6 @@ from time import sleep, time from threading import Event, RLock from typing import Any, Dict, Optional, Union, List, Tuple, cast -from typing import Any, Dict, Optional, Union, List, Tuple, cast -from typing_extensions import Final from math import isclose from serial.serialutil import SerialException # type: ignore @@ -20,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 @@ -34,19 +35,6 @@ ERROR_KEYWORD = 'error' ALARM_KEYWORD = 'alarm' - -# TODO (artyom, ben 20171026): move to config -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 - - PLUNGER_BACKLASH_MM = 0.3 LOW_CURRENT_Z_SPEED = 30 CURRENT_CHANGE_DELAY = 0.005 diff --git a/api/src/opentrons/hardware_control/simulator.py b/api/src/opentrons/hardware_control/simulator.py index a021a5f9510..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,13 +36,6 @@ MODULE_LOG = logging.getLogger(__name__) -_HOME_POSITION: Final = {'X': 418.0, 'Y': 353.0, 'Z': 218.0, - 'A': 218.0, 'B': 19.0, 'C': 19.0} - -_BOUNDS: Final = {'X': 418.0, 'Y': 370.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 @@ -119,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)\ @@ -142,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() @@ -172,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}) @@ -181,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 @@ -291,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 _BOUNDS.items() + return {Axis[ax]: (0, pos) for ax, pos + in self._smoothie_driver.axis_bounds.items() if ax not in 'BC'} @property