From 563be9b5544e5d3020708a6b0d3d28426c8d1172 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 5 Nov 2024 21:56:53 +0200 Subject: [PATCH] feat(robot-context): Add gripper commands --- .../protocols/gripper_controller.py | 3 + .../protocol_api/core/engine/robot.py | 8 ++ api/src/opentrons/protocol_api/core/robot.py | 8 ++ .../opentrons/protocol_api/robot_context.py | 8 +- .../commands/command_unions.py | 10 +++ .../commands/robot/__init__.py | 26 ++++++ .../commands/robot/close_gripper_jaw.py | 79 +++++++++++++++++++ .../commands/robot/open_gripper_jaw.py | 77 ++++++++++++++++++ .../commands/robot/test_close_gripper_jaw.py | 28 +++++++ .../commands/robot/test_open_gripper_jaw.py | 28 +++++++ 10 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 api/src/opentrons/protocol_engine/commands/robot/close_gripper_jaw.py create mode 100644 api/src/opentrons/protocol_engine/commands/robot/open_gripper_jaw.py create mode 100644 api/tests/opentrons/protocol_engine/commands/robot/test_close_gripper_jaw.py create mode 100644 api/tests/opentrons/protocol_engine/commands/robot/test_open_gripper_jaw.py diff --git a/api/src/opentrons/hardware_control/protocols/gripper_controller.py b/api/src/opentrons/hardware_control/protocols/gripper_controller.py index fc81325193c..1b81f4ab460 100644 --- a/api/src/opentrons/hardware_control/protocols/gripper_controller.py +++ b/api/src/opentrons/hardware_control/protocols/gripper_controller.py @@ -14,6 +14,9 @@ async def grip( ) -> None: ... + async def home_gripper_jaw(self) -> None: + ... + async def ungrip(self, force_newtons: Optional[float] = None) -> None: """Release gripped object. diff --git a/api/src/opentrons/protocol_api/core/engine/robot.py b/api/src/opentrons/protocol_api/core/engine/robot.py index 42f8dd2151a..b96ffeb97d7 100644 --- a/api/src/opentrons/protocol_api/core/engine/robot.py +++ b/api/src/opentrons/protocol_api/core/engine/robot.py @@ -152,3 +152,11 @@ def move_axes_relative(self, axis_map: AxisMapType, speed: Optional[float]) -> N self._engine_client.execute_command( cmd.robot.MoveAxesRelativeParams(axis_map=axis_engine_map, speed=speed) ) + + def release_grip(self) -> None: + self._engine_client.execute_command(cmd.robot.openGripperJawParams()) + + def close_gripper(self, force: Optional[float] = None) -> None: + self._engine_client.execute_command( + cmd.robot.closeGripperJawParams(force=force) + ) diff --git a/api/src/opentrons/protocol_api/core/robot.py b/api/src/opentrons/protocol_api/core/robot.py index 95def3e17f3..f65ddbbd7bb 100644 --- a/api/src/opentrons/protocol_api/core/robot.py +++ b/api/src/opentrons/protocol_api/core/robot.py @@ -41,3 +41,11 @@ def move_axes_to( @abstractmethod def move_axes_relative(self, axis_map: AxisMapType, speed: Optional[float]) -> None: ... + + @abstractmethod + def release_grip(self) -> None: + ... + + @abstractmethod + def close_gripper(self, force: Optional[float] = None) -> None: + ... diff --git a/api/src/opentrons/protocol_api/robot_context.py b/api/src/opentrons/protocol_api/robot_context.py index 5b0e578f9bb..df14b8bb7c5 100644 --- a/api/src/opentrons/protocol_api/robot_context.py +++ b/api/src/opentrons/protocol_api/robot_context.py @@ -144,11 +144,13 @@ def move_axes_relative( ) self._core.move_axes_relative(axis_map, speed) - def close_gripper_jaw(self, force: float) -> None: - raise NotImplementedError() + def close_gripper_jaw(self, force: Optional[float] = None) -> None: + """Command the gripper closed with some force.""" + self._core.close_gripper(force) def open_gripper_jaw(self) -> None: - raise NotImplementedError() + """Command the gripper open.""" + self._core.release_grip() def axis_coordinates_for( self, diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index 30f00b4bb95..dc0ebec6ed2 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -406,6 +406,8 @@ robot.MoveTo, robot.MoveAxesRelative, robot.MoveAxesTo, + robot.openGripperJaw, + robot.closeGripperJaw, ], Field(discriminator="commandType"), ] @@ -488,6 +490,8 @@ robot.MoveAxesRelativeParams, robot.MoveAxesToParams, robot.MoveToParams, + robot.openGripperJawParams, + robot.closeGripperJawParams, ] CommandType = Union[ @@ -568,6 +572,8 @@ robot.MoveAxesRelativeCommandType, robot.MoveAxesToCommandType, robot.MoveToCommandType, + robot.openGripperJawCommandType, + robot.closeGripperJawCommandType, ] CommandCreate = Annotated[ @@ -649,6 +655,8 @@ robot.MoveAxesRelativeCreate, robot.MoveAxesToCreate, robot.MoveToCreate, + robot.openGripperJawCreate, + robot.closeGripperJawCreate, ], Field(discriminator="commandType"), ] @@ -731,6 +739,8 @@ robot.MoveAxesRelativeResult, robot.MoveAxesToResult, robot.MoveToResult, + robot.openGripperJawResult, + robot.closeGripperJawResult, ] diff --git a/api/src/opentrons/protocol_engine/commands/robot/__init__.py b/api/src/opentrons/protocol_engine/commands/robot/__init__.py index 5d5fc691e5f..048fecd09fe 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/robot/__init__.py @@ -21,6 +21,20 @@ MoveAxesRelativeResult, MoveAxesRelativeCommandType, ) +from .open_gripper_jaw import ( + openGripperJaw, + openGripperJawCreate, + openGripperJawParams, + openGripperJawResult, + openGripperJawCommandType, +) +from .close_gripper_jaw import ( + closeGripperJaw, + closeGripperJawCreate, + closeGripperJawParams, + closeGripperJawResult, + closeGripperJawCommandType, +) __all__ = [ # robot/moveTo @@ -41,4 +55,16 @@ "MoveAxesRelativeParams", "MoveAxesRelativeResult", "MoveAxesRelativeCommandType", + # robot/openGripperJaw + "openGripperJaw", + "openGripperJawCreate", + "openGripperJawParams", + "openGripperJawResult", + "openGripperJawCommandType", + # robot/closeGripperJaw + "closeGripperJaw", + "closeGripperJawCreate", + "closeGripperJawParams", + "closeGripperJawResult", + "closeGripperJawCommandType", ] diff --git a/api/src/opentrons/protocol_engine/commands/robot/close_gripper_jaw.py b/api/src/opentrons/protocol_engine/commands/robot/close_gripper_jaw.py new file mode 100644 index 00000000000..965c6d2ec72 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/robot/close_gripper_jaw.py @@ -0,0 +1,79 @@ +"""Command models for opening a gripper jaw.""" +from __future__ import annotations +from typing import Literal, Type, Optional +from opentrons.hardware_control import HardwareControlAPI +from opentrons.protocol_engine.resources import ensure_ot3_hardware + +from pydantic import BaseModel, Field + +from ..command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, +) +from opentrons.protocol_engine.errors.error_occurrence import ErrorOccurrence + + +closeGripperJawCommandType = Literal["robot/closeGripperJaw"] + + +class closeGripperJawParams(BaseModel): + """Payload required to close a gripper.""" + + force: Optional[float] = Field( + default=None, + description="The force the gripper should use to hold the jaws, falls to default if none is provided.", + ) + + +class closeGripperJawResult(BaseModel): + """Result data from the execution of a closeGripperJaw command.""" + + pass + + +class closeGripperJawImplementation( + AbstractCommandImpl[closeGripperJawParams, SuccessData[closeGripperJawResult]] +): + """closeGripperJaw command implementation.""" + + def __init__( + self, + hardware_api: HardwareControlAPI, + **kwargs: object, + ) -> None: + self._hardware_api = hardware_api + + async def execute( + self, params: closeGripperJawParams + ) -> SuccessData[closeGripperJawResult]: + """Release the gripper.""" + ot3_hardware_api = ensure_ot3_hardware(self._hardware_api) + await ot3_hardware_api.grip(force_newtons=params.force) + return SuccessData( + public=closeGripperJawResult(), + ) + + +class closeGripperJaw( + BaseCommand[closeGripperJawParams, closeGripperJawResult, ErrorOccurrence] +): + """closeGripperJaw command model.""" + + commandType: closeGripperJawCommandType = "robot/closeGripperJaw" + params: closeGripperJawParams + result: Optional[closeGripperJawResult] + + _ImplementationCls: Type[ + closeGripperJawImplementation + ] = closeGripperJawImplementation + + +class closeGripperJawCreate(BaseCommandCreate[closeGripperJawParams]): + """closeGripperJaw command request model.""" + + commandType: closeGripperJawCommandType = "robot/closeGripperJaw" + params: closeGripperJawParams + + _CommandCls: Type[closeGripperJaw] = closeGripperJaw diff --git a/api/src/opentrons/protocol_engine/commands/robot/open_gripper_jaw.py b/api/src/opentrons/protocol_engine/commands/robot/open_gripper_jaw.py new file mode 100644 index 00000000000..22aa1debd42 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/robot/open_gripper_jaw.py @@ -0,0 +1,77 @@ +"""Command models for opening a gripper jaw.""" +from __future__ import annotations +from typing import Literal, Type, Optional +from opentrons.hardware_control import HardwareControlAPI +from opentrons.protocol_engine.resources import ensure_ot3_hardware + +from pydantic import BaseModel + +from ..command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, +) +from opentrons.protocol_engine.errors.error_occurrence import ErrorOccurrence + + +openGripperJawCommandType = Literal["robot/openGripperJaw"] + + +class openGripperJawParams(BaseModel): + """Payload required to release a gripper.""" + + pass + + +class openGripperJawResult(BaseModel): + """Result data from the execution of a openGripperJaw command.""" + + pass + + +class openGripperJawImplementation( + AbstractCommandImpl[openGripperJawParams, SuccessData[openGripperJawResult]] +): + """openGripperJaw command implementation.""" + + def __init__( + self, + hardware_api: HardwareControlAPI, + **kwargs: object, + ) -> None: + self._hardware_api = hardware_api + + async def execute( + self, params: openGripperJawParams + ) -> SuccessData[openGripperJawResult]: + """Release the gripper.""" + ot3_hardware_api = ensure_ot3_hardware(self._hardware_api) + + await ot3_hardware_api.home_gripper_jaw() + return SuccessData( + public=openGripperJawResult(), + ) + + +class openGripperJaw( + BaseCommand[openGripperJawParams, openGripperJawResult, ErrorOccurrence] +): + """openGripperJaw command model.""" + + commandType: openGripperJawCommandType = "robot/openGripperJaw" + params: openGripperJawParams + result: Optional[openGripperJawResult] + + _ImplementationCls: Type[ + openGripperJawImplementation + ] = openGripperJawImplementation + + +class openGripperJawCreate(BaseCommandCreate[openGripperJawParams]): + """openGripperJaw command request model.""" + + commandType: openGripperJawCommandType = "robot/openGripperJaw" + params: openGripperJawParams + + _CommandCls: Type[openGripperJaw] = openGripperJaw diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_close_gripper_jaw.py b/api/tests/opentrons/protocol_engine/commands/robot/test_close_gripper_jaw.py new file mode 100644 index 00000000000..c5ccd4bf48d --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_close_gripper_jaw.py @@ -0,0 +1,28 @@ +"""Test robot.open-gripper-jaw commands.""" +from decoy import Decoy + +from opentrons.hardware_control import OT3HardwareControlAPI + +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.robot.close_gripper_jaw import ( + closeGripperJawParams, + closeGripperJawResult, + closeGripperJawImplementation, +) + + +async def test_close_gripper_jaw_implementation( + decoy: Decoy, + ot3_hardware_api: OT3HardwareControlAPI, +) -> None: + """Test the `robot.closeGripperJaw` implementation.""" + subject = closeGripperJawImplementation( + hardware_api=ot3_hardware_api, + ) + + params = closeGripperJawParams(force=10) + + result = await subject.execute(params=params) + + assert result == SuccessData(public=closeGripperJawResult()) + decoy.verify(await ot3_hardware_api.grip(force_newtons=10)) diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_open_gripper_jaw.py b/api/tests/opentrons/protocol_engine/commands/robot/test_open_gripper_jaw.py new file mode 100644 index 00000000000..6ded7932963 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_open_gripper_jaw.py @@ -0,0 +1,28 @@ +"""Test robot.open-gripper-jaw commands.""" +from decoy import Decoy + +from opentrons.hardware_control import OT3HardwareControlAPI + +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.robot.open_gripper_jaw import ( + openGripperJawParams, + openGripperJawResult, + openGripperJawImplementation, +) + + +async def test_open_gripper_jaw_implementation( + decoy: Decoy, + ot3_hardware_api: OT3HardwareControlAPI, +) -> None: + """Test the `robot.openGripperJaw` implementation.""" + subject = openGripperJawImplementation( + hardware_api=ot3_hardware_api, + ) + + params = openGripperJawParams() + + result = await subject.execute(params=params) + + assert result == SuccessData(public=openGripperJawResult()) + decoy.verify(await ot3_hardware_api.home_gripper_jaw())