From d6e56f2dd831d201911e4c0a0e6365d8914311af Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Tue, 9 Jul 2024 16:30:08 -0400 Subject: [PATCH 01/22] testing fixes to lld --- api/src/opentrons/protocol_api/core/engine/instrument.py | 4 ++++ api/src/opentrons/protocol_api/instrument_context.py | 8 -------- .../opentrons/protocol_engine/commands/liquid_probe.py | 2 ++ api/src/opentrons/protocol_engine/execution/pipetting.py | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 7b6400bc561..5220fafdd8a 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -850,6 +850,8 @@ def liquid_probe_with_recovery(self, well_core: WellCore) -> None: well_location = WellLocation( origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=0) ) + + self.retract() self._engine_client.execute_command( cmd.LiquidProbeParams( @@ -866,6 +868,8 @@ def liquid_probe_without_recovery(self, well_core: WellCore) -> float: well_location = WellLocation( origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=0) ) + + self.retract() result = self._engine_client.execute_command_without_recovery( cmd.LiquidProbeParams( diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 2c5b1c58160..1ef6d9cd629 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -2055,8 +2055,6 @@ def detect_liquid_presence(self, well: labware.Well) -> bool: :returns: A boolean. """ - if not isinstance(well, labware.Well): - raise WellDoesNotExistError("You must provide a valid well to check.") try: self._core.liquid_probe_without_recovery(well._core) except ProtocolCommandFailedError as e: @@ -2072,9 +2070,6 @@ def require_liquid_presence(self, well: labware.Well) -> None: :returns: None. """ - if not isinstance(well, labware.Well): - raise WellDoesNotExistError("You must provide a valid well to check.") - self._core.liquid_probe_with_recovery(well._core) @requires_version(2, 20) @@ -2087,8 +2082,5 @@ def measure_liquid_height(self, well: labware.Well) -> float: This is intended for Opentrons internal use only and is not a guaranteed API. """ - if not isinstance(well, labware.Well): - raise WellDoesNotExistError("You must provide a valid well to check.") - height = self._core.liquid_probe_without_recovery(well._core) return height diff --git a/api/src/opentrons/protocol_engine/commands/liquid_probe.py b/api/src/opentrons/protocol_engine/commands/liquid_probe.py index 46606415792..ee68917a041 100644 --- a/api/src/opentrons/protocol_engine/commands/liquid_probe.py +++ b/api/src/opentrons/protocol_engine/commands/liquid_probe.py @@ -104,6 +104,8 @@ async def execute(self, params: LiquidProbeParams) -> _ExecuteReturn: labware_id=labware_id, well_name=well_name, ) + + self._movement.retract_axis('leftZ') # liquid_probe process start position position = await self._movement.move_to_well( diff --git a/api/src/opentrons/protocol_engine/execution/pipetting.py b/api/src/opentrons/protocol_engine/execution/pipetting.py index ccf515e1226..413bb62b4da 100644 --- a/api/src/opentrons/protocol_engine/execution/pipetting.py +++ b/api/src/opentrons/protocol_engine/execution/pipetting.py @@ -82,7 +82,7 @@ def __init__(self, state_view: StateView, hardware_api: HardwareControlAPI) -> N def get_is_empty(self, pipette_id: str) -> bool: """Get whether a pipette has a working volume equal to 0.""" - return self._state_view.pipettes.get_working_volume(pipette_id) == 0 + return self._state_view.pipettes.get_aspirated_volume(pipette_id) == 0 def get_is_ready_to_aspirate(self, pipette_id: str) -> bool: """Get whether a pipette is ready to aspirate.""" @@ -235,7 +235,7 @@ def __init__( def get_is_empty(self, pipette_id: str) -> bool: """Get whether a pipette has a working volume equal to 0.""" - return self._state_view.pipettes.get_working_volume(pipette_id) == 0 + return self._state_view.pipettes.get_aspirated_volume(pipette_id) == 0 def get_is_ready_to_aspirate(self, pipette_id: str) -> bool: """Get whether a pipette is ready to aspirate.""" From 6ee855e93ec87fd0df969a9bbf853d9d77608c83 Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Wed, 10 Jul 2024 11:03:22 -0400 Subject: [PATCH 02/22] fixed errors from yesterday. but well is being impaled --- api/lldfunctionexamples.py | 93 +++++++++++++++++++ .../backends/ot3controller.py | 20 ++-- .../protocol_api/core/engine/instrument.py | 24 ++++- .../protocol_api/instrument_context.py | 1 - .../protocol_engine/commands/liquid_probe.py | 2 - .../protocol_engine/state/pipettes.py | 2 +- 6 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 api/lldfunctionexamples.py diff --git a/api/lldfunctionexamples.py b/api/lldfunctionexamples.py new file mode 100644 index 00000000000..3a9844cda95 --- /dev/null +++ b/api/lldfunctionexamples.py @@ -0,0 +1,93 @@ +from opentrons import protocol_api, types +import dataclasses +import typing + + +TIPRACK_NAME = "opentrons_flex_96_tiprack_1000ul" +PLATE_NAME = "nest_96_wellplate_200ul_flat" +RESERVOIR_NAME = "nest_12_reservoir_15ml" +PIPETTE_SINGLE_CHANNEL_NAME = "flex_1channel_1000" +TRASH_NAME = "opentrons_1_trash_1100ml_fixed" + +metadata = { + "protocolName": "Liquid presence detection enable/disable" +} +requirements = { + "robotType": "Flex", + "apiLevel": "2.20" +} + +def run(ctx: protocol_api.ProtocolContext): + tiprack = (ctx.load_labware(TIPRACK_NAME, 'D1')).wells_by_name() + reservoir = (ctx.load_labware(RESERVOIR_NAME, 'C2')).wells_by_name() + plate = (ctx.load_labware(PLATE_NAME, 'D2')).wells_by_name() + trash_bin = ctx.load_trash_bin('B3') + pipette = ctx.load_instrument(PIPETTE_SINGLE_CHANNEL_NAME, mount="left", liquid_presence_detection=False) + assert pipette.liquid_detection == False + + + water = ctx.define_liquid(name="water", description="Normal water", display_color="#42AB2D") + reservoir["A1"].load_liquid(liquid=water, volume=10000) + reservoir["A2"].load_liquid(liquid=water, volume=10000) + reservoir["A3"].load_liquid(liquid=water, volume=10000) + + + # ####### FIRST CYCLE, SHOULD NOT DO LLD ####### + # pipette.pick_up_tip(tiprack["A1"]) + + # # Liquid presence detection does not take place in the following aspirate command + # # because the pipette was loaded in with liquid_presence_detection = False + # pipette.aspirate(5, reservoir["A1"]) + # pipette.dispense(5, plate["A1"]) + + # #Make sure to get a new tip each time before attempting liquid presence detection + # pipette.drop_tip(trash_bin) + + + + + + # ####### SECOND CYCLE, SHOULD NOT DO LLD ####### + # pipette.pick_up_tip(tiprack["A2"]) + + # print(pipette.liquid_detection) #should be: False + # pipette.liquid_detection = True + # print(pipette.liquid_detection) #should be: True + # pipette.aspirate(5, reservoir["A2"]) + # pipette.dispense(5, plate["A1"]) + # pipette.drop_tip(trash_bin) + + + + + ####### THIRD CYCLE, SHOULD DO LLD ####### + pipette.pick_up_tip(tiprack["A3"]) + + print(pipette.liquid_detection) #should be: False + pipette.require_liquid_presence(plate["A2"]) #should run without error + pipette.drop_tip(trash_bin) + + + + + # ####### FOURTH CYCLE, SHOULD TRY TO DO LLD ####### + # pipette.pick_up_tip(tiprack["A4"]) + + # pipette.require_liquid_presence(plate["A2"]) #should error because no liquid in there but provide chance for recovery + + # pipette.drop_tip(trash_bin) + + + + # ####### FIFTH CYCLE, SHOULD DO LLD ####### + # pipette.pick_up_tip(tiprack["A5"]) + + # assert pipette.detect_liquid_presence(plate["A1"]) == True + + # pipette.drop_tip(trash_bin) + # pipette.pick_up_tip(tiprack["A6"]) + + # #assert pipette.detect_liquid_presence(plate["A2"]) == False + + # pipette.drop_tip(trash_bin) + \ No newline at end of file diff --git a/api/src/opentrons/hardware_control/backends/ot3controller.py b/api/src/opentrons/hardware_control/backends/ot3controller.py index fd901955022..e4faefcaae0 100644 --- a/api/src/opentrons/hardware_control/backends/ot3controller.py +++ b/api/src/opentrons/hardware_control/backends/ot3controller.py @@ -1363,16 +1363,16 @@ async def liquid_probe( probe: InstrumentProbeType = InstrumentProbeType.PRIMARY, force_both_sensors: bool = False, ) -> float: - if output_option == OutputOptions.sync_buffer_to_csv: - if ( - self._subsystem_manager.device_info[ - SubSystem.of_mount(mount) - ].revision.tertiary - != "1" - ): - raise UnsupportedHardwareCommand( - "Liquid Probe not supported on this pipette firmware" - ) + # if output_option == OutputOptions.sync_buffer_to_csv: + # if ( + # self._subsystem_manager.device_info[ + # SubSystem.of_mount(mount) + # ].revision.tertiary + # != "1" + # ): + # raise UnsupportedHardwareCommand( + # "Liquid Probe not supported on this pipette firmware" + # ) head_node = axis_to_node(Axis.by_mount(mount)) tool = sensor_node_for_pipette(OT3Mount(mount.value)) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 5220fafdd8a..e34bf271217 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -850,8 +850,16 @@ def liquid_probe_with_recovery(self, well_core: WellCore) -> None: well_location = WellLocation( origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=0) ) - - self.retract() + + loc = Location(well_core.get_top(10), "Well") + + self.move_to( + location=loc, + well_core=well_core, + force_direct=False, + minimum_z_height=None, + speed=None, + ) self._engine_client.execute_command( cmd.LiquidProbeParams( @@ -868,8 +876,16 @@ def liquid_probe_without_recovery(self, well_core: WellCore) -> float: well_location = WellLocation( origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=0) ) - - self.retract() + + loc = Location(well_core.get_top(10), "Well") + + self.move_to( + location=loc, + well_core=well_core, + force_direct=False, + minimum_z_height=None, + speed=None, + ) result = self._engine_client.execute_command_without_recovery( cmd.LiquidProbeParams( diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 1ef6d9cd629..fff8826aa2b 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -9,7 +9,6 @@ CommandParameterLimitViolated, UnexpectedTipRemovalError, ) -from opentrons.protocol_engine.errors.exceptions import WellDoesNotExistError from opentrons.legacy_broker import LegacyBroker from opentrons.hardware_control.dev_types import PipetteDict from opentrons import types diff --git a/api/src/opentrons/protocol_engine/commands/liquid_probe.py b/api/src/opentrons/protocol_engine/commands/liquid_probe.py index ee68917a041..46606415792 100644 --- a/api/src/opentrons/protocol_engine/commands/liquid_probe.py +++ b/api/src/opentrons/protocol_engine/commands/liquid_probe.py @@ -104,8 +104,6 @@ async def execute(self, params: LiquidProbeParams) -> _ExecuteReturn: labware_id=labware_id, well_name=well_name, ) - - self._movement.retract_axis('leftZ') # liquid_probe process start position position = await self._movement.move_to_well( diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index 748786d9bda..cab42ac7238 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -637,7 +637,7 @@ def get_current_tip_lld_settings(self, pipette_id: str) -> float: if attached_tip is None or attached_tip.volume is None: return 0 lld_settings = self.get_pipette_lld_settings(pipette_id) - tipVolume = str(attached_tip.volume) + tipVolume = "t" + str(int(attached_tip.volume)) if ( lld_settings is None or lld_settings[tipVolume] is None From 4f08fd011b4054b6d04ab79dc0a2f2b7916caa67 Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Wed, 10 Jul 2024 14:44:30 -0400 Subject: [PATCH 03/22] minor things --- api/lldfunctionexamples.py | 2 +- api/src/opentrons/hardware_control/backends/ot3controller.py | 1 - api/src/opentrons/protocol_engine/commands/liquid_probe.py | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/lldfunctionexamples.py b/api/lldfunctionexamples.py index 3a9844cda95..033e9370bae 100644 --- a/api/lldfunctionexamples.py +++ b/api/lldfunctionexamples.py @@ -64,7 +64,7 @@ def run(ctx: protocol_api.ProtocolContext): pipette.pick_up_tip(tiprack["A3"]) print(pipette.liquid_detection) #should be: False - pipette.require_liquid_presence(plate["A2"]) #should run without error + pipette.require_liquid_presence(plate["A4"]) #should run without error pipette.drop_tip(trash_bin) diff --git a/api/src/opentrons/hardware_control/backends/ot3controller.py b/api/src/opentrons/hardware_control/backends/ot3controller.py index e4faefcaae0..3b01026cc76 100644 --- a/api/src/opentrons/hardware_control/backends/ot3controller.py +++ b/api/src/opentrons/hardware_control/backends/ot3controller.py @@ -194,7 +194,6 @@ PipetteLiquidNotFoundError, CommunicationError, PythonException, - UnsupportedHardwareCommand, ) from .subsystem_manager import SubsystemManager diff --git a/api/src/opentrons/protocol_engine/commands/liquid_probe.py b/api/src/opentrons/protocol_engine/commands/liquid_probe.py index 46606415792..155a92100f4 100644 --- a/api/src/opentrons/protocol_engine/commands/liquid_probe.py +++ b/api/src/opentrons/protocol_engine/commands/liquid_probe.py @@ -77,8 +77,9 @@ async def execute(self, params: LiquidProbeParams) -> _ExecuteReturn: Return the z-position of the found liquid. Raises: - TipNotAttachedError: if there is not tip attached to the pipette + TipNotAttachedError: if there is no tip attached to the pipette MustHomeError: if the plunger is not in a valid position + TipNotEmptyError: if the tip starts with liquid in it(AKA it's wet and unusable for LLD) LiquidNotFoundError: if liquid is not found during the probe process. """ pipette_id = params.pipetteId From 32873237402d11f1c6788cd656e33febcc1adbe6 Mon Sep 17 00:00:00 2001 From: Ryan howard Date: Wed, 10 Jul 2024 15:20:32 -0400 Subject: [PATCH 04/22] stuff --- api/src/opentrons/config/defaults_ot3.py | 4 ++-- api/src/opentrons/hardware_control/ot3api.py | 15 +++++++++++---- .../hardware_control/tool_sensors.py | 10 ++++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/api/src/opentrons/config/defaults_ot3.py b/api/src/opentrons/config/defaults_ot3.py index e6d91cab081..39b559ea192 100644 --- a/api/src/opentrons/config/defaults_ot3.py +++ b/api/src/opentrons/config/defaults_ot3.py @@ -23,8 +23,8 @@ DEFAULT_MODULE_OFFSET = [0.0, 0.0, 0.0] DEFAULT_LIQUID_PROBE_SETTINGS: Final[LiquidProbeSettings] = LiquidProbeSettings( - mount_speed=10, - plunger_speed=5, + mount_speed=5, + plunger_speed=20, plunger_impulse_time=0.2, sensor_threshold_pascals=15, output_option=OutputOptions.sync_buffer_to_csv, diff --git a/api/src/opentrons/hardware_control/ot3api.py b/api/src/opentrons/hardware_control/ot3api.py index 1c03b49fc6c..821bc15f9bd 100644 --- a/api/src/opentrons/hardware_control/ot3api.py +++ b/api/src/opentrons/hardware_control/ot3api.py @@ -2635,11 +2635,17 @@ async def liquid_probe( error: Optional[PipetteLiquidNotFoundError] = None pos = await self.gantry_position(checked_mount, refresh=True) while (probe_start_pos.z - pos.z) < max_z_dist: + mod_log.info( + f"distance moved{(probe_start_pos.z - pos.z)} start{probe_start_pos.z } current{pos.z} max_z_distance{max_z_dist}" + ) # safe distance so we don't accidentally aspirate liquid if we're already close to liquid - safe_plunger_pos = pos._replace(z=(pos.z + 2)) + safe_plunger_pos = top_types.Point(pos.x, pos.y, pos.z + 2) # overlap amount we want to use between passes - pass_start_pos = pos._replace(z=(pos.z + 0.5)) - + pass_start_pos = top_types.Point(pos.x, pos.y, pos.z + 0.5) + max_z_time = ( + max_z_dist - (probe_start_pos.z - safe_plunger_pos.z) + ) / probe_settings.mount_speed + pass_travel = min(max_z_time * probe_settings.plunger_speed, p_travel) # Prep the plunger await self.move_to(checked_mount, safe_plunger_pos) if probe_settings.aspirate_while_sensing: @@ -2651,11 +2657,12 @@ async def liquid_probe( try: # move to where we want to start a pass and run a pass await self.move_to(checked_mount, pass_start_pos) + mod_log.info(f"max z time {max_z_time} max p travel {pass_travel}") height = await self._liquid_probe_pass( checked_mount, probe_settings, probe if probe else InstrumentProbeType.PRIMARY, - p_travel, + pass_travel, ) # if we made it here without an error we found the liquid error = None diff --git a/hardware/opentrons_hardware/hardware_control/tool_sensors.py b/hardware/opentrons_hardware/hardware_control/tool_sensors.py index c4ea96fccff..e7e5411f1ad 100644 --- a/hardware/opentrons_hardware/hardware_control/tool_sensors.py +++ b/hardware/opentrons_hardware/hardware_control/tool_sensors.py @@ -182,8 +182,10 @@ async def run_sync_buffer_to_csv( ) -> Dict[NodeId, MotorPositionStatus]: """Runs the sensor pass move group and creates a csv file with the results.""" sensor_metadata = [0, 0, mount_speed, plunger_speed, threshold] + LOG.info("running move") positions = await move_group.run(can_messenger=messenger) # wait a little to see the dropoff curve + LOG.info("Stoping sync") await asyncio.sleep(0.15) for sensor_id in log_files.keys(): await messenger.ensure_send( @@ -197,6 +199,7 @@ async def run_sync_buffer_to_csv( ), expected_nodes=[tool], ) + LOG.info("fetching data") for sensor_id in log_files.keys(): sensor_capturer = LogListener( mount=head_node, @@ -402,6 +405,10 @@ async def liquid_probe( ) -> Dict[NodeId, MotorPositionStatus]: """Move the mount and pipette simultaneously while reading from the pressure sensor.""" log_files: Dict[SensorId, str] = {} if not data_files else data_files + LOG.info( + f"/n/n/n/n/nTool Sensors liquid probe head {head_node} p distance {max_p_distance} p speed {plunger_speed} mount_speed {mount_speed} threshold {threshold_pascals} sensor {sensor_id}" + ) + LOG.info(f"sync buffer {sync_buffer_output} can_only {can_bus_only_output} data files {data_files}/n/n/n/n/n") sensor_driver = SensorDriver() threshold_fixed_point = threshold_pascals * sensor_fixed_point_conversion # How many samples to take to level out the sensor @@ -428,6 +435,9 @@ async def liquid_probe( p_prep_distance = float(PLUNGER_SOLO_MOVE_TIME * plunger_speed) p_pass_distance = float(max_p_distance - p_prep_distance) max_z_distance = (p_pass_distance / plunger_speed) * mount_speed + LOG.debug( + f"Tool Sensors liquid probe p prep distance {p_prep_distance} p pass distance {p_pass_distance} max z distance {max_z_distance}" + ) lower_plunger = create_step( distance={tool: float64(p_prep_distance)}, From b0c6dd943087fb88ef844ae61c5ca350a4ec57fc Mon Sep 17 00:00:00 2001 From: Ryan howard Date: Wed, 10 Jul 2024 17:28:08 -0400 Subject: [PATCH 05/22] fix the default config generator for data files --- api/src/opentrons/config/defaults_ot3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/config/defaults_ot3.py b/api/src/opentrons/config/defaults_ot3.py index 39b559ea192..b09235ce35b 100644 --- a/api/src/opentrons/config/defaults_ot3.py +++ b/api/src/opentrons/config/defaults_ot3.py @@ -328,7 +328,7 @@ def _build_default_liquid_probe( or output_option is OutputOptions.stream_to_csv ): data_files = _build_log_files_with_default( - from_conf.get("data_files", {}), default.data_files + from_conf.get("data_files", None), default.data_files ) return LiquidProbeSettings( mount_speed=from_conf.get("mount_speed", default.mount_speed), From ac88e32fdd4441c0c56fe81bf01a3e98d1b0dc63 Mon Sep 17 00:00:00 2001 From: Ryan howard Date: Wed, 10 Jul 2024 17:33:20 -0400 Subject: [PATCH 06/22] remove our debug logging --- api/src/opentrons/hardware_control/ot3api.py | 4 ---- .../hardware_control/tool_sensors.py | 10 ---------- 2 files changed, 14 deletions(-) diff --git a/api/src/opentrons/hardware_control/ot3api.py b/api/src/opentrons/hardware_control/ot3api.py index 821bc15f9bd..32f640269fe 100644 --- a/api/src/opentrons/hardware_control/ot3api.py +++ b/api/src/opentrons/hardware_control/ot3api.py @@ -2635,9 +2635,6 @@ async def liquid_probe( error: Optional[PipetteLiquidNotFoundError] = None pos = await self.gantry_position(checked_mount, refresh=True) while (probe_start_pos.z - pos.z) < max_z_dist: - mod_log.info( - f"distance moved{(probe_start_pos.z - pos.z)} start{probe_start_pos.z } current{pos.z} max_z_distance{max_z_dist}" - ) # safe distance so we don't accidentally aspirate liquid if we're already close to liquid safe_plunger_pos = top_types.Point(pos.x, pos.y, pos.z + 2) # overlap amount we want to use between passes @@ -2657,7 +2654,6 @@ async def liquid_probe( try: # move to where we want to start a pass and run a pass await self.move_to(checked_mount, pass_start_pos) - mod_log.info(f"max z time {max_z_time} max p travel {pass_travel}") height = await self._liquid_probe_pass( checked_mount, probe_settings, diff --git a/hardware/opentrons_hardware/hardware_control/tool_sensors.py b/hardware/opentrons_hardware/hardware_control/tool_sensors.py index e7e5411f1ad..c4ea96fccff 100644 --- a/hardware/opentrons_hardware/hardware_control/tool_sensors.py +++ b/hardware/opentrons_hardware/hardware_control/tool_sensors.py @@ -182,10 +182,8 @@ async def run_sync_buffer_to_csv( ) -> Dict[NodeId, MotorPositionStatus]: """Runs the sensor pass move group and creates a csv file with the results.""" sensor_metadata = [0, 0, mount_speed, plunger_speed, threshold] - LOG.info("running move") positions = await move_group.run(can_messenger=messenger) # wait a little to see the dropoff curve - LOG.info("Stoping sync") await asyncio.sleep(0.15) for sensor_id in log_files.keys(): await messenger.ensure_send( @@ -199,7 +197,6 @@ async def run_sync_buffer_to_csv( ), expected_nodes=[tool], ) - LOG.info("fetching data") for sensor_id in log_files.keys(): sensor_capturer = LogListener( mount=head_node, @@ -405,10 +402,6 @@ async def liquid_probe( ) -> Dict[NodeId, MotorPositionStatus]: """Move the mount and pipette simultaneously while reading from the pressure sensor.""" log_files: Dict[SensorId, str] = {} if not data_files else data_files - LOG.info( - f"/n/n/n/n/nTool Sensors liquid probe head {head_node} p distance {max_p_distance} p speed {plunger_speed} mount_speed {mount_speed} threshold {threshold_pascals} sensor {sensor_id}" - ) - LOG.info(f"sync buffer {sync_buffer_output} can_only {can_bus_only_output} data files {data_files}/n/n/n/n/n") sensor_driver = SensorDriver() threshold_fixed_point = threshold_pascals * sensor_fixed_point_conversion # How many samples to take to level out the sensor @@ -435,9 +428,6 @@ async def liquid_probe( p_prep_distance = float(PLUNGER_SOLO_MOVE_TIME * plunger_speed) p_pass_distance = float(max_p_distance - p_prep_distance) max_z_distance = (p_pass_distance / plunger_speed) * mount_speed - LOG.debug( - f"Tool Sensors liquid probe p prep distance {p_prep_distance} p pass distance {p_pass_distance} max z distance {max_z_distance}" - ) lower_plunger = create_step( distance={tool: float64(p_prep_distance)}, From d7850745a38efa28209f6a6356b81b59d63f15f1 Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Thu, 11 Jul 2024 12:37:56 -0400 Subject: [PATCH 07/22] comment fix --- api/lldfunctionexamples.py | 2 +- .../hardware_control/backends/ot3controller.py | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/api/lldfunctionexamples.py b/api/lldfunctionexamples.py index 033e9370bae..0fb9d346447 100644 --- a/api/lldfunctionexamples.py +++ b/api/lldfunctionexamples.py @@ -64,7 +64,7 @@ def run(ctx: protocol_api.ProtocolContext): pipette.pick_up_tip(tiprack["A3"]) print(pipette.liquid_detection) #should be: False - pipette.require_liquid_presence(plate["A4"]) #should run without error + pipette.require_liquid_presence(plate["A5"]) #should run without error pipette.drop_tip(trash_bin) diff --git a/api/src/opentrons/hardware_control/backends/ot3controller.py b/api/src/opentrons/hardware_control/backends/ot3controller.py index 3b01026cc76..397cf283ae3 100644 --- a/api/src/opentrons/hardware_control/backends/ot3controller.py +++ b/api/src/opentrons/hardware_control/backends/ot3controller.py @@ -1362,17 +1362,6 @@ async def liquid_probe( probe: InstrumentProbeType = InstrumentProbeType.PRIMARY, force_both_sensors: bool = False, ) -> float: - # if output_option == OutputOptions.sync_buffer_to_csv: - # if ( - # self._subsystem_manager.device_info[ - # SubSystem.of_mount(mount) - # ].revision.tertiary - # != "1" - # ): - # raise UnsupportedHardwareCommand( - # "Liquid Probe not supported on this pipette firmware" - # ) - head_node = axis_to_node(Axis.by_mount(mount)) tool = sensor_node_for_pipette(OT3Mount(mount.value)) csv_output = bool(output_option.value & OutputOptions.stream_to_csv.value) From 0daaabfc73145704b90a08ae63c13e31e03c2237 Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Thu, 11 Jul 2024 13:47:29 -0400 Subject: [PATCH 08/22] fixed movement error from tiprack to well --- .../protocol_api/core/engine/instrument.py | 28 ++++--------------- .../protocol_engine/commands/liquid_probe.py | 9 +----- 2 files changed, 7 insertions(+), 30 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index e34bf271217..62e6d4e9ad8 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -850,17 +850,6 @@ def liquid_probe_with_recovery(self, well_core: WellCore) -> None: well_location = WellLocation( origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=0) ) - - loc = Location(well_core.get_top(10), "Well") - - self.move_to( - location=loc, - well_core=well_core, - force_direct=False, - minimum_z_height=None, - speed=None, - ) - self._engine_client.execute_command( cmd.LiquidProbeParams( labwareId=labware_id, @@ -869,6 +858,9 @@ def liquid_probe_with_recovery(self, well_core: WellCore) -> None: pipetteId=self.pipette_id, ) ) + + #TODO: fix this. i'm not even sure what set_last_location is used for + self._protocol_core.set_last_location(location=Location(well_core.get_top(0), "Well"), mount=self.get_mount()) def liquid_probe_without_recovery(self, well_core: WellCore) -> float: labware_id = well_core.labware_id @@ -876,17 +868,6 @@ def liquid_probe_without_recovery(self, well_core: WellCore) -> float: well_location = WellLocation( origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=0) ) - - loc = Location(well_core.get_top(10), "Well") - - self.move_to( - location=loc, - well_core=well_core, - force_direct=False, - minimum_z_height=None, - speed=None, - ) - result = self._engine_client.execute_command_without_recovery( cmd.LiquidProbeParams( labwareId=labware_id, @@ -895,6 +876,9 @@ def liquid_probe_without_recovery(self, well_core: WellCore) -> float: pipetteId=self.pipette_id, ) ) + + #TODO: fix this. i'm not even sure what set_last_location is used for + self._protocol_core.set_last_location(location=Location(well_core.get_top(0), "Well"), mount=self.get_mount()) if result is not None and isinstance(result, LiquidProbeResult): return result.z_position diff --git a/api/src/opentrons/protocol_engine/commands/liquid_probe.py b/api/src/opentrons/protocol_engine/commands/liquid_probe.py index 155a92100f4..0655a06ab22 100644 --- a/api/src/opentrons/protocol_engine/commands/liquid_probe.py +++ b/api/src/opentrons/protocol_engine/commands/liquid_probe.py @@ -10,7 +10,7 @@ from pydantic import Field -from ..types import CurrentWell, DeckPoint +from ..types import CurrentWell, DeckPoint, WellLocation, WellOrigin from .pipetting_common import ( LiquidNotFoundError, LiquidNotFoundErrorInternalData, @@ -100,19 +100,12 @@ async def execute(self, params: LiquidProbeParams) -> _ExecuteReturn: message="Current position of pipette is invalid. Please home." ) - current_well = CurrentWell( - pipette_id=pipette_id, - labware_id=labware_id, - well_name=well_name, - ) - # liquid_probe process start position position = await self._movement.move_to_well( pipette_id=pipette_id, labware_id=labware_id, well_name=well_name, well_location=params.wellLocation, - current_well=current_well, ) try: From 8855fa33f7d2de51dc1e329b4c4b66bd2cc2c1ba Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Thu, 11 Jul 2024 16:35:23 -0400 Subject: [PATCH 09/22] mostly works except detect_liquid_presence errors on empty well instead of returning False --- api/lldfunctionexamples.py | 76 ++++++++----------- .../protocol_api/core/engine/instrument.py | 1 - .../protocol_api/instrument_context.py | 6 +- .../protocol_engine/commands/liquid_probe.py | 2 +- 4 files changed, 38 insertions(+), 47 deletions(-) diff --git a/api/lldfunctionexamples.py b/api/lldfunctionexamples.py index 0fb9d346447..95d72d6fcbc 100644 --- a/api/lldfunctionexamples.py +++ b/api/lldfunctionexamples.py @@ -33,61 +33,49 @@ def run(ctx: protocol_api.ProtocolContext): # ####### FIRST CYCLE, SHOULD NOT DO LLD ####### - # pipette.pick_up_tip(tiprack["A1"]) - - # # Liquid presence detection does not take place in the following aspirate command - # # because the pipette was loaded in with liquid_presence_detection = False - # pipette.aspirate(5, reservoir["A1"]) - # pipette.dispense(5, plate["A1"]) - - # #Make sure to get a new tip each time before attempting liquid presence detection - # pipette.drop_tip(trash_bin) - - - - - - # ####### SECOND CYCLE, SHOULD NOT DO LLD ####### - # pipette.pick_up_tip(tiprack["A2"]) - - # print(pipette.liquid_detection) #should be: False - # pipette.liquid_detection = True - # print(pipette.liquid_detection) #should be: True - # pipette.aspirate(5, reservoir["A2"]) - # pipette.dispense(5, plate["A1"]) - # pipette.drop_tip(trash_bin) + pipette.pick_up_tip(tiprack["A1"]) + pipette.aspirate(100, reservoir["A1"]) + pipette.dispense(100, plate["B2"]) + pipette.drop_tip(trash_bin) + ####### SECOND CYCLE, SHOULD DO LLD ####### + pipette.liquid_detection = True + pipette.pick_up_tip(tiprack["A2"]) + pipette.aspirate(100, reservoir["A2"]) + pipette.dispense(100, plate["A10"]) + pipette.drop_tip(trash_bin) - ####### THIRD CYCLE, SHOULD DO LLD ####### + ####### THIRD CYCLE, SHOULD NOT DO LLD ####### + pipette.liquid_detection = False pipette.pick_up_tip(tiprack["A3"]) - - print(pipette.liquid_detection) #should be: False - pipette.require_liquid_presence(plate["A5"]) #should run without error + pipette.aspirate(100, reservoir["A3"]) + pipette.dispense(100, plate["A11"]) pipette.drop_tip(trash_bin) + ###### FOURTH CYCLE, SHOULD DO LLD ####### + pipette.pick_up_tip(tiprack["A4"]) + pipette.require_liquid_presence(plate["A6"]) #should run without error + pipette.drop_tip(trash_bin) + ####### FIFTH CYCLE, SHOULD TRY TO DO LLD ####### + pipette.pick_up_tip(tiprack["A5"]) + pipette.require_liquid_presence(plate["D10"]) #should error because no liquid in there but provide chance for recovery + pipette.drop_tip(trash_bin) - # ####### FOURTH CYCLE, SHOULD TRY TO DO LLD ####### - # pipette.pick_up_tip(tiprack["A4"]) - - # pipette.require_liquid_presence(plate["A2"]) #should error because no liquid in there but provide chance for recovery - - # pipette.drop_tip(trash_bin) - - - - # ####### FIFTH CYCLE, SHOULD DO LLD ####### - # pipette.pick_up_tip(tiprack["A5"]) - - # assert pipette.detect_liquid_presence(plate["A1"]) == True - # pipette.drop_tip(trash_bin) - # pipette.pick_up_tip(tiprack["A6"]) + ####### SIXTH CYCLE, SHOULD DO LLD ####### + pipette.pick_up_tip(tiprack["A6"]) + assert pipette.detect_liquid_presence(plate["A2"]) is True + pipette.drop_tip(trash_bin) - # #assert pipette.detect_liquid_presence(plate["A2"]) == False - # pipette.drop_tip(trash_bin) + ####### SEVENTH CYCLE, SHOULD DO LLD ####### + pipette.pick_up_tip(tiprack["A7"]) + if pipette.detect_liquid_presence(plate["H7"]) is False: + pipette.drop_tip(trash_bin) + ### THE BELOW LINE IS A PROBLEM + # assert pipette.detect_liquid_presence(plate["H7"]) is False \ No newline at end of file diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 62e6d4e9ad8..5580de7c133 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -876,7 +876,6 @@ def liquid_probe_without_recovery(self, well_core: WellCore) -> float: pipetteId=self.pipette_id, ) ) - #TODO: fix this. i'm not even sure what set_last_location is used for self._protocol_core.set_last_location(location=Location(well_core.get_top(0), "Well"), mount=self.get_mount()) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index fff8826aa2b..5632917c604 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -222,7 +222,6 @@ def aspirate( well: Optional[labware.Well] = None move_to_location: types.Location - last_location = self._get_last_location_by_api_version() try: target = validation.validate_location( @@ -259,6 +258,11 @@ def aspirate( else: c_vol = self._core.get_available_volume() if not volume else volume flow_rate = self._core.get_aspirate_flow_rate(rate) + + if self.api_version >= APIVersion(2, 20) and well is not None: + if self._core.get_liquid_presence_detection(): + self.require_liquid_presence(well=well) + self.prepare_to_aspirate() with publisher.publish_context( broker=self.broker, diff --git a/api/src/opentrons/protocol_engine/commands/liquid_probe.py b/api/src/opentrons/protocol_engine/commands/liquid_probe.py index 0655a06ab22..b3589216bc6 100644 --- a/api/src/opentrons/protocol_engine/commands/liquid_probe.py +++ b/api/src/opentrons/protocol_engine/commands/liquid_probe.py @@ -111,7 +111,7 @@ async def execute(self, params: LiquidProbeParams) -> _ExecuteReturn: try: z_pos = await self._pipetting.liquid_probe_in_place( pipette_id=pipette_id, labware_id=labware_id, well_name=well_name - ) + ) except PipetteLiquidNotFoundError as e: return DefinedErrorData( public=LiquidNotFoundError( From e913351c720a5e137e7b90f56a97a112ef1dab8f Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Fri, 12 Jul 2024 14:32:58 -0400 Subject: [PATCH 10/22] various fixes and improvements --- api/lldfunctionexamples.py | 60 +++++++++---------- .../protocol_api/core/engine/instrument.py | 15 ++--- .../opentrons/protocol_api/core/instrument.py | 8 ++- .../core/legacy/legacy_instrument_core.py | 8 ++- .../legacy_instrument_core.py | 8 ++- .../protocol_api/instrument_context.py | 17 ++++-- .../protocol_engine/commands/liquid_probe.py | 2 +- .../core/engine/test_instrument_core.py | 24 ++------ .../protocol_api/test_instrument_context.py | 19 ++++-- 9 files changed, 89 insertions(+), 72 deletions(-) diff --git a/api/lldfunctionexamples.py b/api/lldfunctionexamples.py index 95d72d6fcbc..188f0f407ad 100644 --- a/api/lldfunctionexamples.py +++ b/api/lldfunctionexamples.py @@ -33,49 +33,49 @@ def run(ctx: protocol_api.ProtocolContext): # ####### FIRST CYCLE, SHOULD NOT DO LLD ####### - pipette.pick_up_tip(tiprack["A1"]) - pipette.aspirate(100, reservoir["A1"]) - pipette.dispense(100, plate["B2"]) - pipette.drop_tip(trash_bin) + # pipette.pick_up_tip(tiprack["A1"]) + # pipette.aspirate(100, reservoir["A1"]) + # pipette.dispense(100, plate["B3"]) + # pipette.drop_tip(trash_bin) - ####### SECOND CYCLE, SHOULD DO LLD ####### - pipette.liquid_detection = True - pipette.pick_up_tip(tiprack["A2"]) - pipette.aspirate(100, reservoir["A2"]) - pipette.dispense(100, plate["A10"]) - pipette.drop_tip(trash_bin) + # ####### SECOND CYCLE, SHOULD DO LLD ####### + # pipette.liquid_detection = True + # pipette.pick_up_tip(tiprack["A2"]) + # pipette.aspirate(100, reservoir["A2"]) + # pipette.dispense(100, plate["A9"]) + # pipette.drop_tip(trash_bin) - ####### THIRD CYCLE, SHOULD NOT DO LLD ####### - pipette.liquid_detection = False - pipette.pick_up_tip(tiprack["A3"]) - pipette.aspirate(100, reservoir["A3"]) - pipette.dispense(100, plate["A11"]) - pipette.drop_tip(trash_bin) + # ####### THIRD CYCLE, SHOULD NOT DO LLD ####### + # pipette.liquid_detection = False + # pipette.pick_up_tip(tiprack["A3"]) + # pipette.aspirate(100, reservoir["A3"]) + # pipette.dispense(100, plate["B11"]) + # pipette.drop_tip(trash_bin) ###### FOURTH CYCLE, SHOULD DO LLD ####### pipette.pick_up_tip(tiprack["A4"]) - pipette.require_liquid_presence(plate["A6"]) #should run without error + pipette.require_liquid_presence(plate["A10"]) #should run without error pipette.drop_tip(trash_bin) ####### FIFTH CYCLE, SHOULD TRY TO DO LLD ####### - pipette.pick_up_tip(tiprack["A5"]) - pipette.require_liquid_presence(plate["D10"]) #should error because no liquid in there but provide chance for recovery - pipette.drop_tip(trash_bin) + # pipette.pick_up_tip(tiprack["A5"]) + # pipette.require_liquid_presence(plate["D10"]) #should error because no liquid in there but provide chance for recovery + # pipette.drop_tip(trash_bin) - ####### SIXTH CYCLE, SHOULD DO LLD ####### - pipette.pick_up_tip(tiprack["A6"]) - assert pipette.detect_liquid_presence(plate["A2"]) is True - pipette.drop_tip(trash_bin) + # ####### SIXTH CYCLE, SHOULD DO LLD ####### + # pipette.pick_up_tip(tiprack["A6"]) + # assert pipette.detect_liquid_presence(plate["A11"]) is True + # pipette.drop_tip(trash_bin) - ####### SEVENTH CYCLE, SHOULD DO LLD ####### - pipette.pick_up_tip(tiprack["A7"]) - if pipette.detect_liquid_presence(plate["H7"]) is False: - pipette.drop_tip(trash_bin) - ### THE BELOW LINE IS A PROBLEM - # assert pipette.detect_liquid_presence(plate["H7"]) is False + # ####### SEVENTH CYCLE, SHOULD DO LLD ####### + # pipette.pick_up_tip(tiprack["A7"]) + # if pipette.detect_liquid_presence(plate["H7"]) is False: + # pipette.drop_tip(trash_bin) + # ### THE BELOW LINE IS A PROBLEM + # # assert pipette.detect_liquid_presence(plate["H7"]) is False \ No newline at end of file diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 5580de7c133..d5e74f3ab36 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -844,7 +844,7 @@ def retract(self) -> None: z_axis = self._engine_client.state.pipettes.get_z_axis(self._pipette_id) self._engine_client.execute_command(cmd.HomeParams(axes=[z_axis])) - def liquid_probe_with_recovery(self, well_core: WellCore) -> None: + def liquid_probe_with_recovery(self, well_core: WellCore, loc: Location) -> None: labware_id = well_core.labware_id well_name = well_core.get_name() well_location = WellLocation( @@ -858,11 +858,12 @@ def liquid_probe_with_recovery(self, well_core: WellCore) -> None: pipetteId=self.pipette_id, ) ) - - #TODO: fix this. i'm not even sure what set_last_location is used for - self._protocol_core.set_last_location(location=Location(well_core.get_top(0), "Well"), mount=self.get_mount()) - def liquid_probe_without_recovery(self, well_core: WellCore) -> float: + self._protocol_core.set_last_location(location=loc, mount=self.get_mount()) + + def liquid_probe_without_recovery( + self, well_core: WellCore, loc: Location + ) -> float: labware_id = well_core.labware_id well_name = well_core.get_name() well_location = WellLocation( @@ -876,8 +877,8 @@ def liquid_probe_without_recovery(self, well_core: WellCore) -> float: pipetteId=self.pipette_id, ) ) - #TODO: fix this. i'm not even sure what set_last_location is used for - self._protocol_core.set_last_location(location=Location(well_core.get_top(0), "Well"), mount=self.get_mount()) + + self._protocol_core.set_last_location(location=loc, mount=self.get_mount()) if result is not None and isinstance(result, LiquidProbeResult): return result.z_position diff --git a/api/src/opentrons/protocol_api/core/instrument.py b/api/src/opentrons/protocol_api/core/instrument.py index 2ad70c7274b..bb510dbaa1c 100644 --- a/api/src/opentrons/protocol_api/core/instrument.py +++ b/api/src/opentrons/protocol_api/core/instrument.py @@ -304,12 +304,16 @@ def retract(self) -> None: ... @abstractmethod - def liquid_probe_with_recovery(self, well_core: WellCoreType) -> None: + def liquid_probe_with_recovery( + self, well_core: WellCoreType, loc: types.Location + ) -> None: """Do a liquid probe to detect the presence of liquid in the well.""" ... @abstractmethod - def liquid_probe_without_recovery(self, well_core: WellCoreType) -> float: + def liquid_probe_without_recovery( + self, well_core: WellCoreType, loc: types.Location + ) -> float: """Do a liquid probe to find the level of the liquid in the well.""" ... diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index 02f0bef1b0a..ccb296e5640 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -571,10 +571,14 @@ def retract(self) -> None: """Retract this instrument to the top of the gantry.""" self._protocol_interface.get_hardware.retract(self._mount) # type: ignore [attr-defined] - def liquid_probe_with_recovery(self, well_core: WellCore) -> None: + def liquid_probe_with_recovery( + self, well_core: WellCore, loc: types.Location + ) -> None: """This will never be called because it was added in API 2.20.""" assert False, "liquid_probe_with_recovery only supported in API 2.20 & later" - def liquid_probe_without_recovery(self, well_core: WellCore) -> float: + def liquid_probe_without_recovery( + self, well_core: WellCore, loc: types.Location + ) -> float: """This will never be called because it was added in API 2.20.""" assert False, "liquid_probe_without_recovery only supported in API 2.20 & later" diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index f53279334a4..bccdc2f5785 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -489,10 +489,14 @@ def retract(self) -> None: """Retract this instrument to the top of the gantry.""" self._protocol_interface.get_hardware.retract(self._mount) # type: ignore [attr-defined] - def liquid_probe_with_recovery(self, well_core: WellCore) -> None: + def liquid_probe_with_recovery( + self, well_core: WellCore, loc: types.Location + ) -> None: """This will never be called because it was added in API 2.20.""" assert False, "liquid_probe_with_recovery only supported in API 2.20 & later" - def liquid_probe_without_recovery(self, well_core: WellCore) -> float: + def liquid_probe_without_recovery( + self, well_core: WellCore, loc: types.Location + ) -> float: """This will never be called because it was added in API 2.20.""" assert False, "liquid_probe_without_recovery only supported in API 2.20 & later" diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 5632917c604..064f8717af1 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -7,6 +7,7 @@ from opentrons_shared_data.errors.exceptions import ( CommandPreconditionViolated, CommandParameterLimitViolated, + StallOrCollisionDetectedError, UnexpectedTipRemovalError, ) from opentrons.legacy_broker import LegacyBroker @@ -258,7 +259,7 @@ def aspirate( else: c_vol = self._core.get_available_volume() if not volume else volume flow_rate = self._core.get_aspirate_flow_rate(rate) - + if self.api_version >= APIVersion(2, 20) and well is not None: if self._core.get_liquid_presence_detection(): self.require_liquid_presence(well=well) @@ -2058,10 +2059,14 @@ def detect_liquid_presence(self, well: labware.Well) -> bool: :returns: A boolean. """ + loc = well.top() try: - self._core.liquid_probe_without_recovery(well._core) + self._core.liquid_probe_without_recovery(well._core, loc) except ProtocolCommandFailedError as e: - if isinstance(e.original_error, LiquidNotFoundError): + # if we handle the error, we change the protocl state from error to valid + if isinstance(e.original_error, LiquidNotFoundError) or isinstance( + e.original_error, StallOrCollisionDetectedError + ): return False raise e else: @@ -2073,7 +2078,8 @@ def require_liquid_presence(self, well: labware.Well) -> None: :returns: None. """ - self._core.liquid_probe_with_recovery(well._core) + loc = well.top() + self._core.liquid_probe_with_recovery(well._core, loc) @requires_version(2, 20) def measure_liquid_height(self, well: labware.Well) -> float: @@ -2085,5 +2091,6 @@ def measure_liquid_height(self, well: labware.Well) -> float: This is intended for Opentrons internal use only and is not a guaranteed API. """ - height = self._core.liquid_probe_without_recovery(well._core) + loc = well.top() + height = self._core.liquid_probe_without_recovery(well._core, loc) return height diff --git a/api/src/opentrons/protocol_engine/commands/liquid_probe.py b/api/src/opentrons/protocol_engine/commands/liquid_probe.py index b3589216bc6..0655a06ab22 100644 --- a/api/src/opentrons/protocol_engine/commands/liquid_probe.py +++ b/api/src/opentrons/protocol_engine/commands/liquid_probe.py @@ -111,7 +111,7 @@ async def execute(self, params: LiquidProbeParams) -> _ExecuteReturn: try: z_pos = await self._pipetting.liquid_probe_in_place( pipette_id=pipette_id, labware_id=labware_id, well_name=well_name - ) + ) except PipetteLiquidNotFoundError as e: return DefinedErrorData( public=LiquidNotFoundError( diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index 3ca12bc004f..3bc7c7d3e32 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -1,6 +1,7 @@ """Test for the ProtocolEngine-based instrument API core.""" from typing import cast, Optional, Union +from opentrons import types from opentrons.protocol_engine.commands.liquid_probe import LiquidProbeResult from opentrons_shared_data.errors.exceptions import PipetteLiquidNotFoundError import pytest @@ -1316,30 +1317,14 @@ def test_liquid_probe_without_recovery( ) ) ).then_raise(PipetteLiquidNotFoundError()) + loc = Location(Point(0, 0, 0), None) try: - subject.liquid_probe_without_recovery(well_core=well_core) + subject.liquid_probe_without_recovery(well_core=well_core, loc=loc) except PipetteLiquidNotFoundError: assert True else: assert False - decoy.reset() - - lpr = LiquidProbeResult(z_position=5.0) - decoy.when( - mock_engine_client.execute_command_without_recovery( - cmd.LiquidProbeParams( - pipetteId=subject.pipette_id, - wellLocation=WellLocation( - origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=0) - ), - wellName=well_core.get_name(), - labwareId=well_core.labware_id, - ) - ) - ).then_return(lpr) - assert subject.liquid_probe_without_recovery(well_core=well_core) == 5.0 - @pytest.mark.parametrize("version", versions_at_or_above(APIVersion(2, 20))) def test_liquid_probe_with_recovery( @@ -1353,7 +1338,8 @@ def test_liquid_probe_with_recovery( well_core = WellCore( name="my cool well", labware_id="123abc", engine_client=mock_engine_client ) - subject.liquid_probe_with_recovery(well_core=well_core) + loc = Location(Point(0, 0, 0), None) + subject.liquid_probe_with_recovery(well_core=well_core, loc=loc) decoy.verify( mock_engine_client.execute_command( cmd.LiquidProbeParams( diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 0d69d462098..d7a77575423 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -1289,7 +1289,9 @@ def test_detect_liquid_presence( message=f"{lnfe.errorType}: {lnfe.detail}", ) decoy.when( - mock_instrument_core.liquid_probe_without_recovery(mock_well._core) + mock_instrument_core.liquid_probe_without_recovery( + mock_well._core, mock_well.top() + ) ).then_raise(errorToRaise) result = subject.detect_liquid_presence(mock_well) assert isinstance(result, bool) @@ -1305,15 +1307,22 @@ def test_require_liquid_presence( ) -> None: """It should raise an exception when called.""" mock_well = decoy.mock(cls=Well) + loc = Location(Point(0, 0, 0), None) lnfe = LiquidNotFoundError(id="1234", createdAt=datetime.now()) errorToRaise = ProtocolCommandFailedError( original_error=lnfe, message=f"{lnfe.errorType}: {lnfe.detail}", ) - decoy.when(mock_instrument_core.liquid_probe_with_recovery(mock_well._core)) + decoy.when( + mock_instrument_core.liquid_probe_with_recovery( + mock_well._core, mock_well.top() + ) + ) subject.require_liquid_presence(mock_well) decoy.when( - mock_instrument_core.liquid_probe_with_recovery(mock_well._core) + mock_instrument_core.liquid_probe_with_recovery( + mock_well._core, mock_well.top() + ) ).then_raise(errorToRaise) with pytest.raises(ProtocolCommandFailedError) as pcfe: subject.require_liquid_presence(mock_well) @@ -1335,7 +1344,9 @@ def test_measure_liquid_height( message=f"{lnfe.errorType}: {lnfe.detail}", ) decoy.when( - mock_instrument_core.liquid_probe_without_recovery(mock_well._core) + mock_instrument_core.liquid_probe_without_recovery( + mock_well._core, mock_well.top() + ) ).then_raise(errorToRaise) with pytest.raises(ProtocolCommandFailedError) as pcfe: subject.measure_liquid_height(mock_well) From 791567bad3f67fd6fce51e2c08af3eb32d3c7055 Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Fri, 12 Jul 2024 14:35:03 -0400 Subject: [PATCH 11/22] minor comment change --- api/src/opentrons/protocol_engine/commands/liquid_probe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_engine/commands/liquid_probe.py b/api/src/opentrons/protocol_engine/commands/liquid_probe.py index 0655a06ab22..b2c7de6f2b8 100644 --- a/api/src/opentrons/protocol_engine/commands/liquid_probe.py +++ b/api/src/opentrons/protocol_engine/commands/liquid_probe.py @@ -79,7 +79,7 @@ async def execute(self, params: LiquidProbeParams) -> _ExecuteReturn: Raises: TipNotAttachedError: if there is no tip attached to the pipette MustHomeError: if the plunger is not in a valid position - TipNotEmptyError: if the tip starts with liquid in it(AKA it's wet and unusable for LLD) + TipNotEmptyError: if the tip starts with liquid in it LiquidNotFoundError: if liquid is not found during the probe process. """ pipette_id = params.pipetteId From b7b4ad8afa0b1c49b1b50e82b76d8d89f31fa097 Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Mon, 15 Jul 2024 08:47:39 -0400 Subject: [PATCH 12/22] minor lint stuff --- api/src/opentrons/protocol_engine/commands/liquid_probe.py | 2 +- .../opentrons/protocol_api/core/engine/test_instrument_core.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/liquid_probe.py b/api/src/opentrons/protocol_engine/commands/liquid_probe.py index b2c7de6f2b8..abac5537e73 100644 --- a/api/src/opentrons/protocol_engine/commands/liquid_probe.py +++ b/api/src/opentrons/protocol_engine/commands/liquid_probe.py @@ -10,7 +10,7 @@ from pydantic import Field -from ..types import CurrentWell, DeckPoint, WellLocation, WellOrigin +from ..types import DeckPoint from .pipetting_common import ( LiquidNotFoundError, LiquidNotFoundErrorInternalData, diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index 3bc7c7d3e32..bcbf0928f62 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -1,8 +1,6 @@ """Test for the ProtocolEngine-based instrument API core.""" from typing import cast, Optional, Union -from opentrons import types -from opentrons.protocol_engine.commands.liquid_probe import LiquidProbeResult from opentrons_shared_data.errors.exceptions import PipetteLiquidNotFoundError import pytest from decoy import Decoy From ef51b873a26f288f14dd6651696bc2a608f972fc Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Mon, 15 Jul 2024 11:51:35 -0400 Subject: [PATCH 13/22] final? except for snapshot --- api/lldfunctionexamples.py | 81 ------------------- .../protocol_api/instrument_context.py | 17 ++-- .../protocol_api/test_instrument_context.py | 5 +- 3 files changed, 12 insertions(+), 91 deletions(-) delete mode 100644 api/lldfunctionexamples.py diff --git a/api/lldfunctionexamples.py b/api/lldfunctionexamples.py deleted file mode 100644 index 188f0f407ad..00000000000 --- a/api/lldfunctionexamples.py +++ /dev/null @@ -1,81 +0,0 @@ -from opentrons import protocol_api, types -import dataclasses -import typing - - -TIPRACK_NAME = "opentrons_flex_96_tiprack_1000ul" -PLATE_NAME = "nest_96_wellplate_200ul_flat" -RESERVOIR_NAME = "nest_12_reservoir_15ml" -PIPETTE_SINGLE_CHANNEL_NAME = "flex_1channel_1000" -TRASH_NAME = "opentrons_1_trash_1100ml_fixed" - -metadata = { - "protocolName": "Liquid presence detection enable/disable" -} -requirements = { - "robotType": "Flex", - "apiLevel": "2.20" -} - -def run(ctx: protocol_api.ProtocolContext): - tiprack = (ctx.load_labware(TIPRACK_NAME, 'D1')).wells_by_name() - reservoir = (ctx.load_labware(RESERVOIR_NAME, 'C2')).wells_by_name() - plate = (ctx.load_labware(PLATE_NAME, 'D2')).wells_by_name() - trash_bin = ctx.load_trash_bin('B3') - pipette = ctx.load_instrument(PIPETTE_SINGLE_CHANNEL_NAME, mount="left", liquid_presence_detection=False) - assert pipette.liquid_detection == False - - - water = ctx.define_liquid(name="water", description="Normal water", display_color="#42AB2D") - reservoir["A1"].load_liquid(liquid=water, volume=10000) - reservoir["A2"].load_liquid(liquid=water, volume=10000) - reservoir["A3"].load_liquid(liquid=water, volume=10000) - - - # ####### FIRST CYCLE, SHOULD NOT DO LLD ####### - # pipette.pick_up_tip(tiprack["A1"]) - # pipette.aspirate(100, reservoir["A1"]) - # pipette.dispense(100, plate["B3"]) - # pipette.drop_tip(trash_bin) - - - # ####### SECOND CYCLE, SHOULD DO LLD ####### - # pipette.liquid_detection = True - # pipette.pick_up_tip(tiprack["A2"]) - # pipette.aspirate(100, reservoir["A2"]) - # pipette.dispense(100, plate["A9"]) - # pipette.drop_tip(trash_bin) - - - # ####### THIRD CYCLE, SHOULD NOT DO LLD ####### - # pipette.liquid_detection = False - # pipette.pick_up_tip(tiprack["A3"]) - # pipette.aspirate(100, reservoir["A3"]) - # pipette.dispense(100, plate["B11"]) - # pipette.drop_tip(trash_bin) - - ###### FOURTH CYCLE, SHOULD DO LLD ####### - pipette.pick_up_tip(tiprack["A4"]) - pipette.require_liquid_presence(plate["A10"]) #should run without error - pipette.drop_tip(trash_bin) - - - ####### FIFTH CYCLE, SHOULD TRY TO DO LLD ####### - # pipette.pick_up_tip(tiprack["A5"]) - # pipette.require_liquid_presence(plate["D10"]) #should error because no liquid in there but provide chance for recovery - # pipette.drop_tip(trash_bin) - - - # ####### SIXTH CYCLE, SHOULD DO LLD ####### - # pipette.pick_up_tip(tiprack["A6"]) - # assert pipette.detect_liquid_presence(plate["A11"]) is True - # pipette.drop_tip(trash_bin) - - - # ####### SEVENTH CYCLE, SHOULD DO LLD ####### - # pipette.pick_up_tip(tiprack["A7"]) - # if pipette.detect_liquid_presence(plate["H7"]) is False: - # pipette.drop_tip(trash_bin) - # ### THE BELOW LINE IS A PROBLEM - # # assert pipette.detect_liquid_presence(plate["H7"]) is False - \ No newline at end of file diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 064f8717af1..d7f6709a5c9 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -260,10 +260,13 @@ def aspirate( c_vol = self._core.get_available_volume() if not volume else volume flow_rate = self._core.get_aspirate_flow_rate(rate) - if self.api_version >= APIVersion(2, 20) and well is not None: - if self._core.get_liquid_presence_detection(): - self.require_liquid_presence(well=well) - self.prepare_to_aspirate() + if ( + self.api_version >= APIVersion(2, 20) + and well is not None + and self.liquid_presence_detection + ): + self.require_liquid_presence(well=well) + self.prepare_to_aspirate() with publisher.publish_context( broker=self.broker, @@ -1678,7 +1681,7 @@ def tip_racks(self, racks: List[labware.Labware]) -> None: @property @requires_version(2, 20) - def liquid_detection(self) -> bool: + def liquid_presence_detection(self) -> bool: """ Gets the global setting for liquid level detection. @@ -1689,9 +1692,9 @@ def liquid_detection(self) -> bool: """ return self._core.get_liquid_presence_detection() - @liquid_detection.setter + @liquid_presence_detection.setter @requires_version(2, 20) - def liquid_detection(self, enable: bool) -> None: + def liquid_presence_detection(self, enable: bool) -> None: self._core.set_liquid_presence_detection(enable) @property diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index d7a77575423..53bd0b441a3 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -1068,8 +1068,8 @@ def test_liquid_presence_detection( ) -> None: """It should have a default liquid presence detection boolean set to False.""" decoy.when(mock_instrument_core.get_liquid_presence_detection()).then_return(False) - assert subject.liquid_detection is False - subject.liquid_detection = True + assert subject.liquid_presence_detection is False + subject.liquid_presence_detection = True decoy.verify(mock_instrument_core.set_liquid_presence_detection(True), times=1) @@ -1307,7 +1307,6 @@ def test_require_liquid_presence( ) -> None: """It should raise an exception when called.""" mock_well = decoy.mock(cls=Well) - loc = Location(Point(0, 0, 0), None) lnfe = LiquidNotFoundError(id="1234", createdAt=datetime.now()) errorToRaise = ProtocolCommandFailedError( original_error=lnfe, From f8593a6bd0472cf1a9438f67848ce19d9b399b97 Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Mon, 15 Jul 2024 12:02:51 -0400 Subject: [PATCH 14/22] test fixes --- .../protocol_engine/commands/test_liquid_probe.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py index a6cea0ab40b..4edbdf5d6be 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py +++ b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py @@ -75,7 +75,6 @@ async def test_liquid_probe_implementation_no_prep( labware_id="123", well_name="A3", well_location=location, - current_well=current_well, ), ).then_return(Point(x=1, y=2, z=3)) @@ -125,8 +124,7 @@ async def test_liquid_probe_implementation_with_prep( pipette_id="abc", labware_id="123", well_name="A3", - well_location=location, - current_well=current_well, + well_location=location ), ).then_return(Point(x=1, y=2, z=3)) @@ -195,8 +193,7 @@ async def test_liquid_not_found_error( pipette_id=pipette_id, labware_id=labware_id, well_name=well_name, - well_location=well_location, - current_well=current_well, + well_location=well_location ), ).then_return(position) From 659786378e195868c03db1f28f9c1a7a12e82ca9 Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Mon, 15 Jul 2024 12:06:13 -0400 Subject: [PATCH 15/22] another test change --- .../opentrons/protocol_engine/commands/test_liquid_probe.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py index 4edbdf5d6be..f2b9fcf7935 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py +++ b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py @@ -148,8 +148,7 @@ async def test_liquid_probe_implementation_with_prep( pipette_id="abc", labware_id="123", well_name="A3", - well_location=WellLocation(origin=WellOrigin.TOP), - current_well=current_well, + well_location=WellLocation(origin=WellOrigin.TOP) ), ) From 55bccd7d68e841017850d719d3591c87b25bb3a2 Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Mon, 15 Jul 2024 12:10:12 -0400 Subject: [PATCH 16/22] formatting --- .../protocol_engine/commands/test_liquid_probe.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py index f2b9fcf7935..478772e84c3 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py +++ b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py @@ -121,10 +121,7 @@ async def test_liquid_probe_implementation_with_prep( ) decoy.when( await movement.move_to_well( - pipette_id="abc", - labware_id="123", - well_name="A3", - well_location=location + pipette_id="abc", labware_id="123", well_name="A3", well_location=location ), ).then_return(Point(x=1, y=2, z=3)) @@ -148,7 +145,7 @@ async def test_liquid_probe_implementation_with_prep( pipette_id="abc", labware_id="123", well_name="A3", - well_location=WellLocation(origin=WellOrigin.TOP) + well_location=WellLocation(origin=WellOrigin.TOP), ), ) @@ -192,7 +189,7 @@ async def test_liquid_not_found_error( pipette_id=pipette_id, labware_id=labware_id, well_name=well_name, - well_location=well_location + well_location=well_location, ), ).then_return(position) From bc9e184d690c0796c9d8949a5dbf38c16f39da07 Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Mon, 15 Jul 2024 12:14:12 -0400 Subject: [PATCH 17/22] please let this be it --- .../opentrons/protocol_engine/commands/test_liquid_probe.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py index 478772e84c3..83bd8a5bf4a 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py +++ b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py @@ -58,7 +58,6 @@ async def test_liquid_probe_implementation_no_prep( ) -> None: """A Liquid Probe should have an execution implementation without preparing to aspirate.""" location = WellLocation(origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1)) - current_well = CurrentWell(pipette_id="abc", labware_id="123", well_name="A3") data = LiquidProbeParams( pipetteId="abc", @@ -103,7 +102,6 @@ async def test_liquid_probe_implementation_with_prep( ) -> None: """A Liquid Probe should have an execution implementation with preparing to aspirate.""" location = WellLocation(origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=0)) - current_well = CurrentWell(pipette_id="abc", labware_id="123", well_name="A3") data = LiquidProbeParams( pipetteId="abc", @@ -164,9 +162,6 @@ async def test_liquid_not_found_error( well_location = WellLocation( origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1) ) - current_well = CurrentWell( - pipette_id=pipette_id, labware_id=labware_id, well_name=well_name - ) position = Point(x=1, y=2, z=3) From 10ba7ac9985d26ef7e704bba7cf18303339effac Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Mon, 15 Jul 2024 12:14:41 -0400 Subject: [PATCH 18/22] ok this is actually it --- .../opentrons/protocol_engine/commands/test_liquid_probe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py index 83bd8a5bf4a..dddf1ca5e06 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py +++ b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py @@ -33,7 +33,7 @@ PipettingHandler, ) from opentrons.protocol_engine.resources.model_utils import ModelUtils -from opentrons.protocol_engine.types import CurrentWell, LoadedPipette +from opentrons.protocol_engine.types import LoadedPipette @pytest.fixture From da72a0e12176b001596d24cf1f911553af568841 Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Mon, 15 Jul 2024 12:37:47 -0400 Subject: [PATCH 19/22] ok this is it --- api/tests/opentrons/hardware_control/test_ot3_api.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/api/tests/opentrons/hardware_control/test_ot3_api.py b/api/tests/opentrons/hardware_control/test_ot3_api.py index fe9f4085436..020e3d0dc3c 100644 --- a/api/tests/opentrons/hardware_control/test_ot3_api.py +++ b/api/tests/opentrons/hardware_control/test_ot3_api.py @@ -806,9 +806,6 @@ async def test_liquid_probe( ) await ot3_hardware.cache_pipette(mount, instr_data, None) pipette = ot3_hardware.hardware_pipettes[mount.to_mount()] - plunger_positions = ot3_hardware._pipette_handler.get_pipette( - mount - ).plunger_positions assert pipette await ot3_hardware.add_tip(mount, 100) @@ -841,7 +838,7 @@ async def test_liquid_probe( mock_move_to_plunger_bottom.assert_called_once() mock_liquid_probe.assert_called_once_with( mount, - plunger_positions.bottom - plunger_positions.top, + 3.0, fake_settings_aspirate.mount_speed, (fake_settings_aspirate.plunger_speed * -1), fake_settings_aspirate.sensor_threshold_pascals, From e4eba630963dc04fcbf204c6e2e870b9081c36c9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 13:17:34 -0400 Subject: [PATCH 20/22] fix(analyses-snapshot-testing): fixing-liquid-probe snapshot failure capture (#15629) This PR is an automated snapshot update request. Please review the changes and merge if they are acceptable or find your bug and fix it. Co-authored-by: aaron-kulkarni <107003644+aaron-kulkarni@users.noreply.github.com> --- ..._v2_16_NO_PIPETTES_AccessToFixedTrashProp].json | 14 +++++++------- ..._v2_16_P1000_96_TC_PartialTipPickupSingle].json | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json index b316741d29b..60a0f1c77a3 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json @@ -24,7 +24,7 @@ "errors": [ { "createdAt": "TIMESTAMP", - "detail": "APIVersionError [line 15]: Fixed Trash is not supported on Flex protocols in API Version 2.16 and above.", + "detail": "UnsupportedAPIError [line 15]: Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16. You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols.", "errorCode": "4000", "errorInfo": {}, "errorType": "ExceptionInProtocolError", @@ -33,14 +33,14 @@ "wrappedErrors": [ { "createdAt": "TIMESTAMP", - "detail": "opentrons.protocols.api_support.util.APIVersionError: Fixed Trash is not supported on Flex protocols in API Version 2.16 and above.", - "errorCode": "4000", + "detail": "Fixed Trash is not available after API version 2.16. You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols.", + "errorCode": "4002", "errorInfo": { - "args": "('Fixed Trash is not supported on Flex protocols in API Version 2.16 and above.',)", - "class": "APIVersionError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line N, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line N, in \n\n File \"Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp.py\", line N, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line N, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line N, in fixed_trash\n raise APIVersionError(\n" + "current_version": "2.16", + "identifier": "Fixed Trash", + "since_version": "2.16" }, - "errorType": "PythonException", + "errorType": "UnsupportedAPIError", "id": "UUID", "isDefined": false, "wrappedErrors": [] diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json index f658c602a39..78da0891438 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json @@ -3535,7 +3535,7 @@ "errors": [ { "createdAt": "TIMESTAMP", - "detail": "ValueError [line 16]: Nozzle layout configuration of style SINGLE is currently unsupported.", + "detail": "ValueError [line 16]: Nozzle layout configuration of style SINGLE is unsupported in API Versions lower than 2.20.", "errorCode": "4000", "errorInfo": {}, "errorType": "ExceptionInProtocolError", @@ -3544,10 +3544,10 @@ "wrappedErrors": [ { "createdAt": "TIMESTAMP", - "detail": "ValueError: Nozzle layout configuration of style SINGLE is currently unsupported.", + "detail": "ValueError: Nozzle layout configuration of style SINGLE is unsupported in API Versions lower than 2.20.", "errorCode": "4000", "errorInfo": { - "args": "('Nozzle layout configuration of style SINGLE is currently unsupported.',)", + "args": "('Nozzle layout configuration of style SINGLE is unsupported in API Versions lower than 2.20.',)", "class": "ValueError", "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line N, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line N, in \n\n File \"Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle.py\", line N, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line N, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line N, in configure_nozzle_layout\n raise ValueError(\n" }, From 833b607e7d17dd7e1a8363013a94d712c47ac0ac Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Mon, 15 Jul 2024 16:31:36 -0400 Subject: [PATCH 21/22] don't catch stalls in detect_liquid_presence --- api/src/opentrons/protocol_api/instrument_context.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index d7f6709a5c9..625518bec9a 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -2067,9 +2067,7 @@ def detect_liquid_presence(self, well: labware.Well) -> bool: self._core.liquid_probe_without_recovery(well._core, loc) except ProtocolCommandFailedError as e: # if we handle the error, we change the protocl state from error to valid - if isinstance(e.original_error, LiquidNotFoundError) or isinstance( - e.original_error, StallOrCollisionDetectedError - ): + if isinstance(e.original_error, LiquidNotFoundError): return False raise e else: From a4163f0a846076f75fb398e23674b7a9d2f66034 Mon Sep 17 00:00:00 2001 From: aaron-kulkarni Date: Mon, 15 Jul 2024 16:49:47 -0400 Subject: [PATCH 22/22] addressing comments --- api/src/opentrons/protocol_api/instrument_context.py | 1 - api/src/opentrons/protocol_engine/execution/pipetting.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 625518bec9a..f1635d20b8f 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -7,7 +7,6 @@ from opentrons_shared_data.errors.exceptions import ( CommandPreconditionViolated, CommandParameterLimitViolated, - StallOrCollisionDetectedError, UnexpectedTipRemovalError, ) from opentrons.legacy_broker import LegacyBroker diff --git a/api/src/opentrons/protocol_engine/execution/pipetting.py b/api/src/opentrons/protocol_engine/execution/pipetting.py index 413bb62b4da..24e45f6c3ad 100644 --- a/api/src/opentrons/protocol_engine/execution/pipetting.py +++ b/api/src/opentrons/protocol_engine/execution/pipetting.py @@ -30,7 +30,7 @@ class PipettingHandler(TypingProtocol): """Liquid handling commands.""" def get_is_empty(self, pipette_id: str) -> bool: - """Get whether a pipette has a working volume equal to 0.""" + """Get whether a pipette has an aspirated volume equal to 0.""" def get_is_ready_to_aspirate(self, pipette_id: str) -> bool: """Get whether a pipette is ready to aspirate.""" @@ -81,7 +81,7 @@ def __init__(self, state_view: StateView, hardware_api: HardwareControlAPI) -> N self._hardware_api = hardware_api def get_is_empty(self, pipette_id: str) -> bool: - """Get whether a pipette has a working volume equal to 0.""" + """Get whether a pipette has an aspirated volume equal to 0.""" return self._state_view.pipettes.get_aspirated_volume(pipette_id) == 0 def get_is_ready_to_aspirate(self, pipette_id: str) -> bool: @@ -234,7 +234,7 @@ def __init__( self._state_view = state_view def get_is_empty(self, pipette_id: str) -> bool: - """Get whether a pipette has a working volume equal to 0.""" + """Get whether a pipette has an aspirated volume equal to 0.""" return self._state_view.pipettes.get_aspirated_volume(pipette_id) == 0 def get_is_ready_to_aspirate(self, pipette_id: str) -> bool: