diff --git a/api/src/opentrons/hardware_control/backends/ot3controller.py b/api/src/opentrons/hardware_control/backends/ot3controller.py index 71e713b50e6..cc37fccbec4 100644 --- a/api/src/opentrons/hardware_control/backends/ot3controller.py +++ b/api/src/opentrons/hardware_control/backends/ot3controller.py @@ -660,8 +660,11 @@ async def gripper_grip_jaw( self, duty_cycle: float, stop_condition: MoveStopCondition = MoveStopCondition.none, + stay_engaged: bool = True, ) -> None: - move_group = create_gripper_jaw_grip_group(duty_cycle, stop_condition) + move_group = create_gripper_jaw_grip_group( + duty_cycle, stop_condition, stay_engaged + ) runner = MoveGroupRunner(move_groups=[move_group]) positions = await runner.run(can_messenger=self._messenger) self._handle_motor_status_response(positions) diff --git a/api/src/opentrons/hardware_control/backends/ot3simulator.py b/api/src/opentrons/hardware_control/backends/ot3simulator.py index 84f88f6d788..492749ec010 100644 --- a/api/src/opentrons/hardware_control/backends/ot3simulator.py +++ b/api/src/opentrons/hardware_control/backends/ot3simulator.py @@ -353,9 +353,10 @@ async def gripper_grip_jaw( self, duty_cycle: float, stop_condition: MoveStopCondition = MoveStopCondition.none, + stay_engaged: bool = True, ) -> None: """Move gripper inward.""" - _ = create_gripper_jaw_grip_group(duty_cycle, stop_condition) + _ = create_gripper_jaw_grip_group(duty_cycle, stop_condition, stay_engaged) @ensure_yield async def gripper_home_jaw(self, duty_cycle: float) -> None: diff --git a/api/src/opentrons/hardware_control/backends/ot3utils.py b/api/src/opentrons/hardware_control/backends/ot3utils.py index de6525c5d4d..8cfdbe60a6b 100644 --- a/api/src/opentrons/hardware_control/backends/ot3utils.py +++ b/api/src/opentrons/hardware_control/backends/ot3utils.py @@ -428,12 +428,14 @@ def create_gear_motor_home_group( def create_gripper_jaw_grip_group( duty_cycle: float, stop_condition: MoveStopCondition = MoveStopCondition.none, + stay_engaged: bool = True, ) -> MoveGroup: step = create_gripper_jaw_step( duration=np.float64(GRIPPER_JAW_GRIP_TIME), duty_cycle=np.float32(round(duty_cycle)), stop_condition=stop_condition, move_type=MoveType.grip, + stay_engaged=stay_engaged, ) move_group: MoveGroup = [step] return move_group diff --git a/api/src/opentrons/hardware_control/ot3api.py b/api/src/opentrons/hardware_control/ot3api.py index 5401c65ee72..4d6f06ca578 100644 --- a/api/src/opentrons/hardware_control/ot3api.py +++ b/api/src/opentrons/hardware_control/ot3api.py @@ -1395,10 +1395,12 @@ async def update_config(self, **kwargs: Any) -> None: self._config = replace(self._config, **kwargs) @ExecutionManagerProvider.wait_for_running - async def _grip(self, duty_cycle: float) -> None: + async def _grip(self, duty_cycle: float, stay_engaged: bool = True) -> None: """Move the gripper jaw inward to close.""" try: - await self._backend.gripper_grip_jaw(duty_cycle=duty_cycle) + await self._backend.gripper_grip_jaw( + duty_cycle=duty_cycle, stay_engaged=stay_engaged + ) await self._cache_encoder_position() except Exception: self._log.exception( @@ -1431,12 +1433,14 @@ async def _hold_jaw_width(self, jaw_width_mm: float) -> None: self._log.exception("Gripper set width failed") raise - async def grip(self, force_newtons: Optional[float] = None) -> None: + async def grip( + self, force_newtons: Optional[float] = None, stay_engaged: bool = True + ) -> None: self._gripper_handler.check_ready_for_jaw_move() dc = self._gripper_handler.get_duty_cycle_by_grip_force( force_newtons or self._gripper_handler.get_gripper().default_grip_force ) - await self._grip(duty_cycle=dc) + await self._grip(duty_cycle=dc, stay_engaged=stay_engaged) self._gripper_handler.set_jaw_state(GripperJawState.GRIPPING) async def ungrip(self, force_newtons: Optional[float] = None) -> None: diff --git a/api/src/opentrons/protocol_engine/execution/labware_movement.py b/api/src/opentrons/protocol_engine/execution/labware_movement.py index 3c5cd2f42a6..7ec8fa33d08 100644 --- a/api/src/opentrons/protocol_engine/execution/labware_movement.py +++ b/api/src/opentrons/protocol_engine/execution/labware_movement.py @@ -141,7 +141,7 @@ async def move_labware_with_gripper( # Keep the gripper in idly gripped position to avoid colliding with # things like the thermocycler latches - await ot3api.grip(force_newtons=IDLE_STATE_GRIP_FORCE) + await ot3api.grip(force_newtons=IDLE_STATE_GRIP_FORCE, stay_engaged=False) async def ensure_movement_not_obstructed_by_module( self, labware_id: str, new_location: LabwareLocation diff --git a/api/tests/opentrons/hardware_control/test_ot3_api.py b/api/tests/opentrons/hardware_control/test_ot3_api.py index 05a6859a6d5..4f71a00c9d2 100644 --- a/api/tests/opentrons/hardware_control/test_ot3_api.py +++ b/api/tests/opentrons/hardware_control/test_ot3_api.py @@ -966,6 +966,7 @@ async def test_gripper_action_works_with_gripper( await ot3_hardware.grip(5.0) mock_grip.assert_called_once_with( gc.duty_cycle_by_force(5.0, gripper_config.grip_force_profile), + stay_engaged=True, ) await ot3_hardware.ungrip() diff --git a/api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py b/api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py index b332c1bf0a7..e3265f0382e 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py @@ -258,7 +258,9 @@ async def test_move_labware_with_gripper( await ot3_hardware_api.move_to( mount=gripper, abs_position=expected_waypoints[5] ), - await ot3_hardware_api.grip(force_newtons=IDLE_STATE_GRIP_FORCE), + await ot3_hardware_api.grip( + force_newtons=IDLE_STATE_GRIP_FORCE, stay_engaged=False + ), ) diff --git a/hardware/opentrons_hardware/firmware_bindings/messages/payloads.py b/hardware/opentrons_hardware/firmware_bindings/messages/payloads.py index 7ae5d3fe530..fbbdb1ec4e7 100644 --- a/hardware/opentrons_hardware/firmware_bindings/messages/payloads.py +++ b/hardware/opentrons_hardware/firmware_bindings/messages/payloads.py @@ -526,6 +526,7 @@ class GripperMoveRequestPayload(AddToMoveGroupRequestPayload): duty_cycle: utils.UInt32Field encoder_position_um: utils.Int32Field + stay_engaged: utils.UInt8Field @dataclass(eq=False) diff --git a/hardware/opentrons_hardware/hardware_control/gripper_settings.py b/hardware/opentrons_hardware/hardware_control/gripper_settings.py index 332413e5f77..9635ed2ef13 100644 --- a/hardware/opentrons_hardware/hardware_control/gripper_settings.py +++ b/hardware/opentrons_hardware/hardware_control/gripper_settings.py @@ -128,6 +128,7 @@ async def grip( seq_id: int, duration_sec: float, duty_cycle: int, + stay_engaged: bool = False, ) -> None: """Start grip motion.""" await can_messenger.send( @@ -141,6 +142,7 @@ async def grip( ), duty_cycle=UInt32Field(duty_cycle), encoder_position_um=Int32Field(0), + stay_engaged=UInt8Field(int(stay_engaged)), ) ), ) @@ -162,6 +164,7 @@ async def home( duration=UInt32Field(0), duty_cycle=UInt32Field(duty_cycle), encoder_position_um=Int32Field(0), + stay_engaged=UInt8Field(0), ) ), ) @@ -173,6 +176,7 @@ async def move( seq_id: int, duty_cycle: int, encoder_position_um: int, + stay_engaged: bool = False, ) -> None: """Start linear motion.""" await can_messenger.send( @@ -184,6 +188,7 @@ async def move( duration=UInt32Field(0), duty_cycle=UInt32Field(duty_cycle), encoder_position_um=Int32Field(encoder_position_um), + stay_engaged=UInt8Field(int(stay_engaged)), ) ), ) diff --git a/hardware/opentrons_hardware/hardware_control/motion.py b/hardware/opentrons_hardware/hardware_control/motion.py index 9ed3d5f2211..2809170ec61 100644 --- a/hardware/opentrons_hardware/hardware_control/motion.py +++ b/hardware/opentrons_hardware/hardware_control/motion.py @@ -71,6 +71,7 @@ class MoveGroupSingleGripperStep: pwm_duty_cycle: np.float32 encoder_position_um: np.int32 pwm_frequency: np.float32 = np.float32(320000) + stay_engaged: bool = False stop_condition: MoveStopCondition = MoveStopCondition.gripper_force move_type: MoveType = MoveType.grip @@ -214,6 +215,7 @@ def create_gripper_jaw_step( duty_cycle: np.float32, encoder_position_um: np.int32 = np.int32(0), frequency: np.float32 = np.float32(320000), + stay_engaged: bool = False, stop_condition: MoveStopCondition = MoveStopCondition.gripper_force, move_type: MoveType = MoveType.grip, ) -> MoveGroupStep: @@ -223,6 +225,7 @@ def create_gripper_jaw_step( duration_sec=duration, pwm_frequency=frequency, pwm_duty_cycle=duty_cycle, + stay_engaged=stay_engaged, stop_condition=stop_condition, move_type=move_type, encoder_position_um=encoder_position_um, diff --git a/hardware/opentrons_hardware/hardware_control/move_group_runner.py b/hardware/opentrons_hardware/hardware_control/move_group_runner.py index d26759dddb8..c812e344c34 100644 --- a/hardware/opentrons_hardware/hardware_control/move_group_runner.py +++ b/hardware/opentrons_hardware/hardware_control/move_group_runner.py @@ -301,6 +301,7 @@ def _get_brushed_motor_message( ), duty_cycle=UInt32Field(int(step.pwm_duty_cycle)), encoder_position_um=Int32Field(int(step.encoder_position_um)), + stay_engaged=UInt8Field(int(step.stay_engaged)), ) if step.move_type == MoveType.home: return GripperHomeRequest(payload=payload) diff --git a/hardware/opentrons_hardware/scripts/gripper_lifetime.py b/hardware/opentrons_hardware/scripts/gripper_lifetime.py index 5d16856ca1f..28838537454 100644 --- a/hardware/opentrons_hardware/scripts/gripper_lifetime.py +++ b/hardware/opentrons_hardware/scripts/gripper_lifetime.py @@ -102,6 +102,7 @@ async def run(args: argparse.Namespace) -> None: pwm_frequency=float32(pwm_freq), pwm_duty_cycle=float32(pwm_duty), encoder_position_um=int32(0), + stay_engaged=True, ) } ] diff --git a/hardware/tests/opentrons_hardware/hardware_control/test_move_group_runner.py b/hardware/tests/opentrons_hardware/hardware_control/test_move_group_runner.py index 5f86a8c8867..281accb908d 100644 --- a/hardware/tests/opentrons_hardware/hardware_control/test_move_group_runner.py +++ b/hardware/tests/opentrons_hardware/hardware_control/test_move_group_runner.py @@ -191,6 +191,7 @@ def move_group_gripper_multiple() -> MoveGroups: duration_sec=float64(1), pwm_duty_cycle=float32(50), encoder_position_um=int32(0), + stay_engaged=True, stop_condition=MoveStopCondition.gripper_force, move_type=MoveType.grip, ), @@ -203,6 +204,7 @@ def move_group_gripper_multiple() -> MoveGroups: duration_sec=float64(1), pwm_duty_cycle=float32(50), encoder_position_um=int32(80000), + stay_engaged=False, stop_condition=MoveStopCondition.encoder_position, move_type=MoveType.linear, ),