diff --git a/api/src/opentrons/hardware_control/__init__.py b/api/src/opentrons/hardware_control/__init__.py index 012ae2931f2..f03403c21e4 100644 --- a/api/src/opentrons/hardware_control/__init__.py +++ b/api/src/opentrons/hardware_control/__init__.py @@ -293,6 +293,8 @@ def current_position(self, mount: top_types.Mount) -> Dict[Axis, float]: This returns cached position to avoid hitting the smoothie driver unless ``refresh`` is ``True``. """ + if not self._current_position: + raise MustHomeError if mount == mount.RIGHT: offset = top_types.Point(0, 0, 0) else: diff --git a/api/src/opentrons/protocol_api/contexts.py b/api/src/opentrons/protocol_api/contexts.py index 41adaf2c45d..bb30453a178 100644 --- a/api/src/opentrons/protocol_api/contexts.py +++ b/api/src/opentrons/protocol_api/contexts.py @@ -197,40 +197,28 @@ def update_config(self, **kwargs): """ self._hardware.update_config(**kwargs) - def move_to(self, mount: types.Mount, - location: types.Location): - """ Implement motions of the robot. - - This should not need to be called by the user; it is called by - :py:meth:`InstrumentContext.move_to` (and thus all other - :py:class:`InstrumentContext` methods that involve moving, such as - :py:meth:`InstrumentContext.aspirate`) to move the pipettes around. - - It encapsulates location caching and ensures that all moves are safe. - It does this by taking a :py:class:`.types.Location` that can have - a position attached to it, and its behavior depends on the state of - that location cache and the passed location. - """ - if self._location_cache: - from_lw = self._location_cache.labware - else: - from_lw = None - from_loc = types.Location(self._hardware.gantry_position(mount), - from_lw) - moves = geometry.plan_moves(from_loc, location, self._deck_layout) - self._log.debug("planned moves for {}->{}: {}" - .format(from_loc, location, moves)) - self._location_cache = location - self._last_moved_instrument = mount - for move in moves: - self._hardware.move_to(mount, move) - def home(self): """ Homes the robot. """ self._log.debug("home") self._hardware.home() + @property + def location_cache(self) -> Optional[types.Location]: + """ The cache used by the robot to determine where it last was. + """ + return self._location_cache + + @location_cache.setter + def location_cache(self, loc: Optional[types.Location]): + self._location_cache = loc + + @property + def deck(self) -> geometry.Deck: + """ The object holding the deck layout of the robot. + """ + return self._deck_layout + @staticmethod def _build_hardware_adapter( loop: asyncio.AbstractEventLoop, @@ -434,13 +422,22 @@ def transfer(self, raise NotImplementedError def move_to(self, location: types.Location): - """ Move this pipette to a specific location on the deck. + """ Move the instrument. - :param location: Where to move to. - :raises ValueError: if an argument is incorrect. + :param location: The location to move to. """ - self._log.debug("move to {}".format(location)) - self._ctx.move_to(self._mount, location) + if self._ctx.location_cache: + from_lw = self._ctx.location_cache.labware + else: + from_lw = None + from_loc = types.Location(self._hardware.gantry_position(self._mount), + from_lw) + moves = geometry.plan_moves(from_loc, location, self._ctx.deck) + self._log.debug("move {}->{}: {}" + .format(from_loc, location, moves)) + self._ctx.location_cache = location + for move in moves: + self._hardware.move_to(self._mount, move) return self @property diff --git a/api/tests/opentrons/protocol_api/test_context.py b/api/tests/opentrons/protocol_api/test_context.py index 4e74df395dd..e21c9400623 100644 --- a/api/tests/opentrons/protocol_api/test_context.py +++ b/api/tests/opentrons/protocol_api/test_context.py @@ -54,7 +54,6 @@ def test_location_cache(loop, monkeypatch, load_my_labware): ctx = papi.ProtocolContext(loop) ctx.connect(hardware) right = ctx.load_instrument('p10_single', Mount.RIGHT) - left = ctx.load_instrument('p300_multi', Mount.LEFT) lw = ctx.load_labware_by_name('generic_96_wellPlate_380_uL', 1) ctx.home() @@ -77,21 +76,16 @@ def fake_plan_move(from_loc, to_loc, deck, assert test_args[0].point == Point(418, 353, 205) assert test_args[0].labware is None - # Once we have a location cache, that should be our from_loc + # kOnce we have a location cache, that should be our from_loc right.move_to(lw.wells()[1].top()) - assert test_args[0] == lw.wells()[0].top() - - # If we switch instruments, we should ignore the cache - here = hardware.gantry_position(Mount.LEFT) - left.move_to(lw.wells()[1].top()) - assert test_args[0].point == here - assert test_args[0].labware is None + assert test_args[0].labware == lw.wells()[0] def test_move_uses_arc(loop, monkeypatch, load_my_labware): hardware = API.build_hardware_simulator(loop=loop) ctx = papi.ProtocolContext(loop) ctx.connect(hardware) + ctx.home() right = ctx.load_instrument('p10_single', Mount.RIGHT) lw = ctx.load_labware_by_name('generic_96_wellPlate_380_uL', 1) ctx.home() @@ -122,6 +116,7 @@ def test_pipette_info(loop): def test_aspirate(loop, load_my_labware, monkeypatch): ctx = papi.ProtocolContext(loop) + ctx.home() lw = ctx.load_labware_by_name('generic_96_wellPlate_380_uL', 1) instr = ctx.load_instrument('p10_single', Mount.RIGHT) @@ -138,19 +133,18 @@ def fake_move(mount, loc): move_called_with = (mount, loc) monkeypatch.setattr(ctx._hardware._api, 'aspirate', fake_hw_aspirate) - monkeypatch.setattr(ctx, 'move_to', fake_move) + monkeypatch.setattr(ctx._hardware._api, 'move_to', fake_move) instr.aspirate(2.0, lw.wells()[0].bottom()) assert asp_called_with == (Mount.RIGHT, 2.0, 1.0) - assert move_called_with == (Mount.RIGHT, lw.wells()[0].bottom()) + assert move_called_with == (Mount.RIGHT, lw.wells()[0].bottom().point) instr.well_bottom_clearance = 1.0 instr.aspirate(2.0, lw.wells()[0]) dest_point, dest_lw = lw.wells()[0].bottom() dest_point = dest_point._replace(z=dest_point.z + 1.0) - assert move_called_with == (Mount.RIGHT, - Location(dest_point, dest_lw)) + assert move_called_with == (Mount.RIGHT, dest_point) move_called_with = None instr.aspirate(2.0) @@ -159,6 +153,7 @@ def fake_move(mount, loc): def test_dispense(loop, load_my_labware, monkeypatch): ctx = papi.ProtocolContext(loop) + ctx.home() lw = ctx.load_labware_by_name('generic_96_wellPlate_380_uL', 1) instr = ctx.load_instrument('p10_single', Mount.RIGHT) @@ -175,19 +170,18 @@ def fake_move(mount, loc): move_called_with = (mount, loc) monkeypatch.setattr(ctx._hardware._api, 'dispense', fake_hw_dispense) - monkeypatch.setattr(ctx, 'move_to', fake_move) + monkeypatch.setattr(ctx._hardware._api, 'move_to', fake_move) instr.dispense(2.0, lw.wells()[0].bottom()) assert disp_called_with == (Mount.RIGHT, 2.0, 1.0) - assert move_called_with == (Mount.RIGHT, lw.wells()[0].bottom()) + assert move_called_with == (Mount.RIGHT, lw.wells()[0].bottom().point) instr.well_bottom_clearance = 1.0 instr.dispense(2.0, lw.wells()[0]) dest_point, dest_lw = lw.wells()[0].bottom() dest_point = dest_point._replace(z=dest_point.z + 1.0) - assert move_called_with == (Mount.RIGHT, - Location(dest_point, dest_lw)) + assert move_called_with == (Mount.RIGHT, dest_point) move_called_with = None instr.dispense(2.0)