opentrons 8.3.2__py2.py3-none-any.whl → 8.4.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/calibration_storage/ot2/mark_bad_calibration.py +2 -0
- opentrons/calibration_storage/ot2/tip_length.py +6 -6
- opentrons/config/advanced_settings.py +9 -11
- opentrons/config/feature_flags.py +0 -4
- opentrons/config/reset.py +7 -2
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/async_serial.py +4 -0
- opentrons/drivers/asyncio/communication/errors.py +41 -8
- opentrons/drivers/asyncio/communication/serial_connection.py +36 -10
- opentrons/drivers/flex_stacker/__init__.py +9 -3
- opentrons/drivers/flex_stacker/abstract.py +140 -15
- opentrons/drivers/flex_stacker/driver.py +593 -47
- opentrons/drivers/flex_stacker/errors.py +64 -0
- opentrons/drivers/flex_stacker/simulator.py +222 -24
- opentrons/drivers/flex_stacker/types.py +211 -15
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/execute.py +4 -2
- opentrons/hardware_control/api.py +5 -0
- opentrons/hardware_control/backends/flex_protocol.py +4 -0
- opentrons/hardware_control/backends/ot3controller.py +12 -1
- opentrons/hardware_control/backends/ot3simulator.py +3 -0
- opentrons/hardware_control/backends/subsystem_manager.py +8 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +10 -6
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +59 -6
- opentrons/hardware_control/modules/__init__.py +12 -1
- opentrons/hardware_control/modules/absorbance_reader.py +11 -9
- opentrons/hardware_control/modules/flex_stacker.py +498 -0
- opentrons/hardware_control/modules/heater_shaker.py +12 -10
- opentrons/hardware_control/modules/magdeck.py +5 -1
- opentrons/hardware_control/modules/tempdeck.py +5 -1
- opentrons/hardware_control/modules/thermocycler.py +15 -14
- opentrons/hardware_control/modules/types.py +191 -1
- opentrons/hardware_control/modules/utils.py +3 -0
- opentrons/hardware_control/motion_utilities.py +20 -0
- opentrons/hardware_control/ot3api.py +145 -15
- opentrons/hardware_control/protocols/liquid_handler.py +47 -1
- opentrons/hardware_control/types.py +6 -0
- opentrons/legacy_commands/commands.py +102 -5
- opentrons/legacy_commands/helpers.py +74 -1
- opentrons/legacy_commands/types.py +33 -2
- opentrons/protocol_api/__init__.py +2 -0
- opentrons/protocol_api/_liquid.py +39 -8
- opentrons/protocol_api/_liquid_properties.py +20 -19
- opentrons/protocol_api/_transfer_liquid_validation.py +91 -0
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +11 -1
- opentrons/protocol_api/core/engine/instrument.py +1356 -107
- opentrons/protocol_api/core/engine/labware.py +8 -4
- opentrons/protocol_api/core/engine/load_labware_params.py +68 -10
- opentrons/protocol_api/core/engine/module_core.py +118 -2
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +6 -14
- opentrons/protocol_api/core/engine/protocol.py +253 -11
- opentrons/protocol_api/core/engine/stringify.py +19 -8
- opentrons/protocol_api/core/engine/transfer_components_executor.py +858 -0
- opentrons/protocol_api/core/engine/well.py +73 -5
- opentrons/protocol_api/core/instrument.py +71 -21
- opentrons/protocol_api/core/labware.py +6 -2
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +76 -49
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +27 -2
- opentrons/protocol_api/core/legacy/load_info.py +4 -12
- opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
- opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +73 -23
- opentrons/protocol_api/core/module.py +43 -0
- opentrons/protocol_api/core/protocol.py +33 -0
- opentrons/protocol_api/core/well.py +23 -2
- opentrons/protocol_api/instrument_context.py +454 -150
- opentrons/protocol_api/labware.py +98 -50
- opentrons/protocol_api/module_contexts.py +140 -0
- opentrons/protocol_api/protocol_context.py +163 -19
- opentrons/protocol_api/validation.py +51 -41
- opentrons/protocol_engine/__init__.py +21 -2
- opentrons/protocol_engine/actions/actions.py +5 -5
- opentrons/protocol_engine/clients/sync_client.py +6 -0
- opentrons/protocol_engine/commands/__init__.py +66 -36
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
- opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
- opentrons/protocol_engine/commands/aspirate.py +6 -2
- opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +210 -0
- opentrons/protocol_engine/commands/blow_out.py +2 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
- opentrons/protocol_engine/commands/command_unions.py +102 -33
- opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
- opentrons/protocol_engine/commands/dispense.py +3 -1
- opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
- opentrons/protocol_engine/commands/drop_tip.py +23 -1
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
- opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
- opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
- opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +291 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
- opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
- opentrons/protocol_engine/commands/liquid_probe.py +27 -13
- opentrons/protocol_engine/commands/load_labware.py +42 -39
- opentrons/protocol_engine/commands/load_lid.py +21 -13
- opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
- opentrons/protocol_engine/commands/load_module.py +18 -17
- opentrons/protocol_engine/commands/load_pipette.py +3 -0
- opentrons/protocol_engine/commands/move_labware.py +139 -20
- opentrons/protocol_engine/commands/move_to_well.py +5 -11
- opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
- opentrons/protocol_engine/commands/pipetting_common.py +159 -8
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +15 -5
- opentrons/protocol_engine/commands/{evotip_dispense.py → pressure_dispense.py} +33 -34
- opentrons/protocol_engine/commands/reload_labware.py +6 -19
- opentrons/protocol_engine/commands/{evotip_seal_pipette.py → seal_pipette_to_tip.py} +97 -76
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
- opentrons/protocol_engine/commands/{evotip_unseal_pipette.py → unseal_pipette_from_tip.py} +31 -40
- opentrons/protocol_engine/errors/__init__.py +10 -0
- opentrons/protocol_engine/errors/exceptions.py +62 -0
- opentrons/protocol_engine/execution/equipment.py +123 -106
- opentrons/protocol_engine/execution/labware_movement.py +8 -6
- opentrons/protocol_engine/execution/pipetting.py +235 -25
- opentrons/protocol_engine/execution/tip_handler.py +82 -32
- opentrons/protocol_engine/labware_offset_standardization.py +194 -0
- opentrons/protocol_engine/protocol_engine.py +22 -13
- opentrons/protocol_engine/resources/deck_configuration_provider.py +98 -2
- opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
- opentrons/protocol_engine/resources/labware_validation.py +7 -5
- opentrons/protocol_engine/slot_standardization.py +11 -23
- opentrons/protocol_engine/state/addressable_areas.py +84 -46
- opentrons/protocol_engine/state/frustum_helpers.py +36 -14
- opentrons/protocol_engine/state/geometry.py +892 -227
- opentrons/protocol_engine/state/labware.py +252 -55
- opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
- opentrons/protocol_engine/state/modules.py +210 -67
- opentrons/protocol_engine/state/pipettes.py +54 -0
- opentrons/protocol_engine/state/state.py +1 -1
- opentrons/protocol_engine/state/tips.py +14 -0
- opentrons/protocol_engine/state/update_types.py +180 -25
- opentrons/protocol_engine/state/wells.py +55 -9
- opentrons/protocol_engine/types/__init__.py +300 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +72 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +111 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +111 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +131 -0
- opentrons/protocol_engine/types/location.py +194 -0
- opentrons/protocol_engine/types/module.py +301 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +124 -0
- opentrons/protocol_reader/extract_labware_definitions.py +7 -3
- opentrons/protocol_reader/file_format_validator.py +5 -3
- opentrons/protocol_runner/json_translator.py +4 -2
- opentrons/protocol_runner/legacy_command_mapper.py +6 -2
- opentrons/protocol_runner/run_orchestrator.py +4 -1
- opentrons/protocols/advanced_control/transfers/common.py +48 -1
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +16 -3
- opentrons/protocols/labware.py +27 -23
- opentrons/protocols/models/__init__.py +0 -21
- opentrons/simulate.py +4 -2
- opentrons/types.py +20 -7
- opentrons/util/logging_config.py +94 -25
- opentrons/util/logging_queue_handler.py +61 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/METADATA +4 -4
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/RECORD +192 -151
- opentrons/calibration_storage/ot2/models/defaults.py +0 -0
- opentrons/calibration_storage/ot3/models/defaults.py +0 -0
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_engine/types.py +0 -1311
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/LICENSE +0 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/WHEEL +0 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Models and implementation for the ``moveLabware`` command."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
from typing import TYPE_CHECKING, Optional, Type, Any
|
|
4
|
+
from typing import TYPE_CHECKING, Optional, Type, Any, List
|
|
5
|
+
from typing_extensions import TypedDict # note: need this instead of typing for py<3.12
|
|
5
6
|
|
|
6
7
|
from pydantic.json_schema import SkipJsonSchema
|
|
7
8
|
from pydantic import BaseModel, Field
|
|
@@ -18,7 +19,7 @@ from opentrons.types import Point
|
|
|
18
19
|
from ..types import (
|
|
19
20
|
ModuleModel,
|
|
20
21
|
CurrentWell,
|
|
21
|
-
|
|
22
|
+
LoadableLabwareLocation,
|
|
22
23
|
DeckSlotLocation,
|
|
23
24
|
ModuleLocation,
|
|
24
25
|
OnLabwareLocation,
|
|
@@ -26,6 +27,9 @@ from ..types import (
|
|
|
26
27
|
LabwareMovementStrategy,
|
|
27
28
|
LabwareOffsetVector,
|
|
28
29
|
LabwareMovementOffsetData,
|
|
30
|
+
LabwareLocationSequence,
|
|
31
|
+
NotOnDeckLocationSequenceComponent,
|
|
32
|
+
OFF_DECK_LOCATION,
|
|
29
33
|
)
|
|
30
34
|
from ..errors import (
|
|
31
35
|
LabwareMovementNotAllowedError,
|
|
@@ -64,7 +68,9 @@ class MoveLabwareParams(BaseModel):
|
|
|
64
68
|
"""Input parameters for a ``moveLabware`` command."""
|
|
65
69
|
|
|
66
70
|
labwareId: str = Field(..., description="The ID of the labware to move.")
|
|
67
|
-
newLocation:
|
|
71
|
+
newLocation: LoadableLabwareLocation = Field(
|
|
72
|
+
..., description="Where to move the labware."
|
|
73
|
+
)
|
|
68
74
|
strategy: LabwareMovementStrategy = Field(
|
|
69
75
|
...,
|
|
70
76
|
description="Whether to use the gripper to perform the labware movement"
|
|
@@ -100,6 +106,31 @@ class MoveLabwareResult(BaseModel):
|
|
|
100
106
|
" so the default of (0, 0, 0) will be used."
|
|
101
107
|
),
|
|
102
108
|
)
|
|
109
|
+
eventualDestinationLocationSequence: LabwareLocationSequence | None = Field(
|
|
110
|
+
None,
|
|
111
|
+
description=(
|
|
112
|
+
"The full location in which this labware will eventually reside. This will typically be the same as its "
|
|
113
|
+
"immediate destination, but if this labware is going to the trash then this field will be off deck."
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
immediateDestinationLocationSequence: LabwareLocationSequence | None = Field(
|
|
117
|
+
None,
|
|
118
|
+
description=(
|
|
119
|
+
"The full location to which this labware is being moved, right now."
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
originLocationSequence: LabwareLocationSequence | None = Field(
|
|
123
|
+
None,
|
|
124
|
+
description="The full location down to the deck of the labware before this command.",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class ErrorDetails(TypedDict):
|
|
129
|
+
"""Location details for a failed gripper move."""
|
|
130
|
+
|
|
131
|
+
originLocationSequence: LabwareLocationSequence
|
|
132
|
+
immediateDestinationLocationSequence: LabwareLocationSequence
|
|
133
|
+
eventualDestinationLocationSequence: LabwareLocationSequence
|
|
103
134
|
|
|
104
135
|
|
|
105
136
|
class GripperMovementError(ErrorOccurrence):
|
|
@@ -112,6 +143,8 @@ class GripperMovementError(ErrorOccurrence):
|
|
|
112
143
|
|
|
113
144
|
errorType: Literal["gripperMovement"] = "gripperMovement"
|
|
114
145
|
|
|
146
|
+
errorInfo: ErrorDetails
|
|
147
|
+
|
|
115
148
|
|
|
116
149
|
_ExecuteReturn = SuccessData[MoveLabwareResult] | DefinedErrorData[GripperMovementError]
|
|
117
150
|
|
|
@@ -152,6 +185,11 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
152
185
|
f"Cannot move fixed trash labware '{current_labware_definition.parameters.loadName}'."
|
|
153
186
|
)
|
|
154
187
|
|
|
188
|
+
origin_location_sequence = self._state_view.geometry.get_location_sequence(
|
|
189
|
+
params.labwareId
|
|
190
|
+
)
|
|
191
|
+
eventual_destination_location_sequence: LabwareLocationSequence | None = None
|
|
192
|
+
|
|
155
193
|
if isinstance(params.newLocation, AddressableAreaLocation):
|
|
156
194
|
area_name = params.newLocation.addressableAreaName
|
|
157
195
|
if (
|
|
@@ -181,9 +219,19 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
181
219
|
y=0,
|
|
182
220
|
z=0,
|
|
183
221
|
)
|
|
222
|
+
eventual_destination_location_sequence = [
|
|
223
|
+
NotOnDeckLocationSequenceComponent(
|
|
224
|
+
logicalLocationName=OFF_DECK_LOCATION
|
|
225
|
+
)
|
|
226
|
+
]
|
|
184
227
|
elif fixture_validation.is_trash(area_name):
|
|
185
228
|
# When dropping labware in the trash bins we want to ensure they are lids
|
|
186
229
|
# and enforce a y-axis drop offset to ensure they fall within the trash bin
|
|
230
|
+
eventual_destination_location_sequence = [
|
|
231
|
+
NotOnDeckLocationSequenceComponent(
|
|
232
|
+
logicalLocationName=OFF_DECK_LOCATION
|
|
233
|
+
)
|
|
234
|
+
]
|
|
187
235
|
if labware_validation.validate_definition_is_lid(
|
|
188
236
|
self._state_view.labware.get_definition(params.labwareId)
|
|
189
237
|
):
|
|
@@ -220,7 +268,7 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
220
268
|
)
|
|
221
269
|
|
|
222
270
|
# Check that labware and destination do not have labware on top
|
|
223
|
-
self._state_view.labware.
|
|
271
|
+
self._state_view.labware.raise_if_labware_has_non_lid_labware_on_top(
|
|
224
272
|
labware_id=params.labwareId
|
|
225
273
|
)
|
|
226
274
|
|
|
@@ -282,7 +330,6 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
282
330
|
raise LabwareMovementNotAllowedError(
|
|
283
331
|
f"Cannot move adapter '{current_labware_definition.parameters.loadName}' with gripper."
|
|
284
332
|
)
|
|
285
|
-
|
|
286
333
|
validated_current_loc = (
|
|
287
334
|
self._state_view.geometry.ensure_valid_gripper_location(
|
|
288
335
|
current_labware.location
|
|
@@ -299,6 +346,16 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
299
346
|
if trash_lid_drop_offset:
|
|
300
347
|
user_offset_data.dropOffset += trash_lid_drop_offset
|
|
301
348
|
|
|
349
|
+
immediate_destination_location_sequence = (
|
|
350
|
+
self._state_view.geometry.get_predicted_location_sequence(
|
|
351
|
+
validated_new_loc
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
if eventual_destination_location_sequence is None:
|
|
355
|
+
eventual_destination_location_sequence = (
|
|
356
|
+
immediate_destination_location_sequence
|
|
357
|
+
)
|
|
358
|
+
|
|
302
359
|
try:
|
|
303
360
|
# Skips gripper moves when using virtual gripper
|
|
304
361
|
await self._labware_movement.move_labware_with_gripper(
|
|
@@ -315,20 +372,23 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
315
372
|
# todo(mm, 2024-09-26): Catch LabwareNotPickedUpError when that exists and
|
|
316
373
|
# move_labware_with_gripper() raises it.
|
|
317
374
|
) as exception:
|
|
318
|
-
gripper_movement_error: GripperMovementError | None = (
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
375
|
+
gripper_movement_error: GripperMovementError | None = GripperMovementError(
|
|
376
|
+
id=self._model_utils.generate_id(),
|
|
377
|
+
createdAt=self._model_utils.get_timestamp(),
|
|
378
|
+
errorCode=exception.code.value.code,
|
|
379
|
+
detail=exception.code.value.detail,
|
|
380
|
+
errorInfo={
|
|
381
|
+
"originLocationSequence": origin_location_sequence,
|
|
382
|
+
"immediateDestinationLocationSequence": immediate_destination_location_sequence,
|
|
383
|
+
"eventualDestinationLocationSequence": eventual_destination_location_sequence,
|
|
384
|
+
},
|
|
385
|
+
wrappedErrors=[
|
|
386
|
+
ErrorOccurrence.from_failed(
|
|
387
|
+
id=self._model_utils.generate_id(),
|
|
388
|
+
createdAt=self._model_utils.get_timestamp(),
|
|
389
|
+
error=exception,
|
|
390
|
+
)
|
|
391
|
+
],
|
|
332
392
|
)
|
|
333
393
|
else:
|
|
334
394
|
gripper_movement_error = None
|
|
@@ -344,7 +404,27 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
344
404
|
|
|
345
405
|
elif params.strategy == LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE:
|
|
346
406
|
# Pause to allow for manual labware movement
|
|
407
|
+
immediate_destination_location_sequence = (
|
|
408
|
+
self._state_view.geometry.get_predicted_location_sequence(
|
|
409
|
+
params.newLocation
|
|
410
|
+
)
|
|
411
|
+
)
|
|
412
|
+
if eventual_destination_location_sequence is None:
|
|
413
|
+
eventual_destination_location_sequence = (
|
|
414
|
+
immediate_destination_location_sequence
|
|
415
|
+
)
|
|
416
|
+
|
|
347
417
|
await self._run_control.wait_for_resume()
|
|
418
|
+
else:
|
|
419
|
+
immediate_destination_location_sequence = (
|
|
420
|
+
self._state_view.geometry.get_predicted_location_sequence(
|
|
421
|
+
params.newLocation
|
|
422
|
+
)
|
|
423
|
+
)
|
|
424
|
+
if eventual_destination_location_sequence is None:
|
|
425
|
+
eventual_destination_location_sequence = (
|
|
426
|
+
immediate_destination_location_sequence
|
|
427
|
+
)
|
|
348
428
|
|
|
349
429
|
# We may have just moved the labware that contains the current well out from
|
|
350
430
|
# under the pipette. Clear the current location to reflect the fact that the
|
|
@@ -364,8 +444,47 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
364
444
|
new_offset_id=new_offset_id,
|
|
365
445
|
)
|
|
366
446
|
|
|
447
|
+
if labware_validation.validate_definition_is_lid(
|
|
448
|
+
definition=self._state_view.labware.get_definition(params.labwareId)
|
|
449
|
+
):
|
|
450
|
+
parent_updates: List[str] = []
|
|
451
|
+
lid_updates: List[str | None] = []
|
|
452
|
+
# when moving a lid between locations we need to:
|
|
453
|
+
if (
|
|
454
|
+
isinstance(current_labware.location, OnLabwareLocation)
|
|
455
|
+
and self._state_view.labware.get_lid_by_labware_id(
|
|
456
|
+
current_labware.location.labwareId
|
|
457
|
+
)
|
|
458
|
+
is not None
|
|
459
|
+
):
|
|
460
|
+
# if the source location was a parent labware and not a lid stack or lid, update the parent labware lid ID to None (no more lid)
|
|
461
|
+
parent_updates.append(current_labware.location.labwareId)
|
|
462
|
+
lid_updates.append(None)
|
|
463
|
+
|
|
464
|
+
# If we're moving to a non lid object, add to the setlids list of things to do
|
|
465
|
+
if isinstance(
|
|
466
|
+
available_new_location, OnLabwareLocation
|
|
467
|
+
) and not labware_validation.validate_definition_is_lid(
|
|
468
|
+
self._state_view.labware.get_definition(
|
|
469
|
+
available_new_location.labwareId
|
|
470
|
+
)
|
|
471
|
+
):
|
|
472
|
+
parent_updates.append(available_new_location.labwareId)
|
|
473
|
+
lid_updates.append(params.labwareId)
|
|
474
|
+
# Add to setlids
|
|
475
|
+
if len(parent_updates) > 0:
|
|
476
|
+
state_update.set_lids(
|
|
477
|
+
parent_labware_ids=parent_updates,
|
|
478
|
+
lid_ids=lid_updates,
|
|
479
|
+
)
|
|
480
|
+
|
|
367
481
|
return SuccessData(
|
|
368
|
-
public=MoveLabwareResult(
|
|
482
|
+
public=MoveLabwareResult(
|
|
483
|
+
offsetId=new_offset_id,
|
|
484
|
+
originLocationSequence=origin_location_sequence,
|
|
485
|
+
immediateDestinationLocationSequence=immediate_destination_location_sequence,
|
|
486
|
+
eventualDestinationLocationSequence=eventual_destination_location_sequence,
|
|
487
|
+
),
|
|
369
488
|
state_update=state_update,
|
|
370
489
|
)
|
|
371
490
|
|
|
@@ -8,7 +8,7 @@ from .pipetting_common import (
|
|
|
8
8
|
PipetteIdMixin,
|
|
9
9
|
)
|
|
10
10
|
from .movement_common import (
|
|
11
|
-
|
|
11
|
+
LiquidHandlingWellLocationMixin,
|
|
12
12
|
MovementMixin,
|
|
13
13
|
DestinationPositionResult,
|
|
14
14
|
StallOrCollisionError,
|
|
@@ -21,7 +21,6 @@ from .command import (
|
|
|
21
21
|
SuccessData,
|
|
22
22
|
DefinedErrorData,
|
|
23
23
|
)
|
|
24
|
-
from ..errors import LabwareIsTipRackError
|
|
25
24
|
|
|
26
25
|
if TYPE_CHECKING:
|
|
27
26
|
from ..execution import MovementHandler
|
|
@@ -31,7 +30,7 @@ if TYPE_CHECKING:
|
|
|
31
30
|
MoveToWellCommandType = Literal["moveToWell"]
|
|
32
31
|
|
|
33
32
|
|
|
34
|
-
class MoveToWellParams(PipetteIdMixin,
|
|
33
|
+
class MoveToWellParams(PipetteIdMixin, LiquidHandlingWellLocationMixin, MovementMixin):
|
|
35
34
|
"""Payload required to move a pipette to a specific well."""
|
|
36
35
|
|
|
37
36
|
pass
|
|
@@ -70,14 +69,9 @@ class MoveToWellImplementation(
|
|
|
70
69
|
labware_id = params.labwareId
|
|
71
70
|
well_name = params.wellName
|
|
72
71
|
well_location = params.wellLocation
|
|
73
|
-
|
|
74
|
-
if
|
|
75
|
-
|
|
76
|
-
and well_location.volumeOffset
|
|
77
|
-
):
|
|
78
|
-
raise LabwareIsTipRackError(
|
|
79
|
-
"Cannot specify a WellLocation with a volumeOffset with movement to a tip rack"
|
|
80
|
-
)
|
|
72
|
+
# TODO(cm): implement move_to_well with meniscus + volume offset
|
|
73
|
+
if well_location.volumeOffset and well_location.volumeOffset != 0:
|
|
74
|
+
raise ValueError("volume offset not supported with MoveToWell")
|
|
81
75
|
|
|
82
76
|
move_result = await move_to_well(
|
|
83
77
|
model_utils=self._model_utils,
|
|
@@ -150,7 +150,7 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
|
|
|
150
150
|
pipette_id=pipette_id,
|
|
151
151
|
tip_geometry=e.tip_geometry,
|
|
152
152
|
)
|
|
153
|
-
.set_fluid_empty(pipette_id=pipette_id)
|
|
153
|
+
.set_fluid_empty(pipette_id=pipette_id, clean_tip=True)
|
|
154
154
|
.mark_tips_as_used(
|
|
155
155
|
pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
|
|
156
156
|
)
|
|
@@ -188,7 +188,10 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
|
|
|
188
188
|
.mark_tips_as_used(
|
|
189
189
|
pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
|
|
190
190
|
)
|
|
191
|
-
.set_fluid_empty(pipette_id=pipette_id)
|
|
191
|
+
.set_fluid_empty(pipette_id=pipette_id, clean_tip=True)
|
|
192
|
+
.set_pipette_ready_to_aspirate(
|
|
193
|
+
pipette_id=pipette_id, ready_to_aspirate=True
|
|
194
|
+
)
|
|
192
195
|
)
|
|
193
196
|
return SuccessData(
|
|
194
197
|
public=PickUpTipResult(
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Common pipetting command base models."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
from typing import Literal, Tuple, TYPE_CHECKING
|
|
5
|
-
|
|
4
|
+
from typing import Literal, Tuple, TYPE_CHECKING, Optional
|
|
5
|
+
import numpy
|
|
6
6
|
from typing_extensions import TypedDict
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
|
|
@@ -20,6 +20,10 @@ if TYPE_CHECKING:
|
|
|
20
20
|
from ..notes import CommandNoteAdder
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
DEFAULT_CORRECTION_VOLUME = 0.0
|
|
24
|
+
"""Default correction volume (uL) for any aspirate/ dispense volume."""
|
|
25
|
+
|
|
26
|
+
|
|
23
27
|
class PipetteIdMixin(BaseModel):
|
|
24
28
|
"""Mixin for command requests that take a pipette ID."""
|
|
25
29
|
|
|
@@ -41,6 +45,10 @@ class AspirateVolumeMixin(BaseModel):
|
|
|
41
45
|
" There is some tolerance for floating point rounding errors.",
|
|
42
46
|
ge=0,
|
|
43
47
|
)
|
|
48
|
+
correctionVolume: Optional[float] = Field(
|
|
49
|
+
None,
|
|
50
|
+
description="The correction volume in uL.",
|
|
51
|
+
)
|
|
44
52
|
|
|
45
53
|
|
|
46
54
|
class DispenseVolumeMixin(BaseModel):
|
|
@@ -53,6 +61,10 @@ class DispenseVolumeMixin(BaseModel):
|
|
|
53
61
|
" There is some tolerance for floating point rounding errors.",
|
|
54
62
|
ge=0,
|
|
55
63
|
)
|
|
64
|
+
correctionVolume: Optional[float] = Field(
|
|
65
|
+
None,
|
|
66
|
+
description="The correction volume in uL.",
|
|
67
|
+
)
|
|
56
68
|
|
|
57
69
|
|
|
58
70
|
class FlowRateMixin(BaseModel):
|
|
@@ -168,7 +180,11 @@ async def prepare_for_aspirate(
|
|
|
168
180
|
else:
|
|
169
181
|
return SuccessData(
|
|
170
182
|
public=EmptyResult(),
|
|
171
|
-
state_update=StateUpdate()
|
|
183
|
+
state_update=StateUpdate()
|
|
184
|
+
.set_fluid_empty(pipette_id=pipette_id)
|
|
185
|
+
.set_pipette_ready_to_aspirate(
|
|
186
|
+
pipette_id=pipette_id, ready_to_aspirate=True
|
|
187
|
+
),
|
|
172
188
|
)
|
|
173
189
|
|
|
174
190
|
|
|
@@ -176,18 +192,69 @@ async def aspirate_in_place(
|
|
|
176
192
|
pipette_id: str,
|
|
177
193
|
volume: float,
|
|
178
194
|
flow_rate: float,
|
|
195
|
+
correction_volume: float,
|
|
179
196
|
location_if_error: ErrorLocationInfo,
|
|
180
197
|
command_note_adder: CommandNoteAdder,
|
|
181
198
|
pipetting: PipettingHandler,
|
|
182
199
|
model_utils: ModelUtils,
|
|
183
200
|
) -> SuccessData[BaseLiquidHandlingResult] | DefinedErrorData[OverpressureError]:
|
|
184
|
-
"""Execute an aspirate in place
|
|
201
|
+
"""Execute an aspirate in place micro-operation."""
|
|
185
202
|
try:
|
|
186
203
|
volume_aspirated = await pipetting.aspirate_in_place(
|
|
187
204
|
pipette_id=pipette_id,
|
|
188
205
|
volume=volume,
|
|
189
206
|
flow_rate=flow_rate,
|
|
190
207
|
command_note_adder=command_note_adder,
|
|
208
|
+
correction_volume=correction_volume,
|
|
209
|
+
)
|
|
210
|
+
except PipetteOverpressureError as e:
|
|
211
|
+
return DefinedErrorData(
|
|
212
|
+
public=OverpressureError(
|
|
213
|
+
id=model_utils.generate_id(),
|
|
214
|
+
createdAt=model_utils.get_timestamp(),
|
|
215
|
+
wrappedErrors=[
|
|
216
|
+
ErrorOccurrence.from_failed(
|
|
217
|
+
id=model_utils.generate_id(),
|
|
218
|
+
createdAt=model_utils.get_timestamp(),
|
|
219
|
+
error=e,
|
|
220
|
+
)
|
|
221
|
+
],
|
|
222
|
+
errorInfo=location_if_error,
|
|
223
|
+
),
|
|
224
|
+
state_update=StateUpdate().set_fluid_unknown(pipette_id=pipette_id),
|
|
225
|
+
)
|
|
226
|
+
else:
|
|
227
|
+
return SuccessData(
|
|
228
|
+
public=BaseLiquidHandlingResult(
|
|
229
|
+
volume=volume_aspirated,
|
|
230
|
+
),
|
|
231
|
+
state_update=StateUpdate().set_fluid_aspirated(
|
|
232
|
+
pipette_id=pipette_id,
|
|
233
|
+
fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=volume_aspirated),
|
|
234
|
+
),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
async def aspirate_while_tracking(
|
|
239
|
+
pipette_id: str,
|
|
240
|
+
labware_id: str,
|
|
241
|
+
well_name: str,
|
|
242
|
+
volume: float,
|
|
243
|
+
flow_rate: float,
|
|
244
|
+
location_if_error: ErrorLocationInfo,
|
|
245
|
+
command_note_adder: CommandNoteAdder,
|
|
246
|
+
pipetting: PipettingHandler,
|
|
247
|
+
model_utils: ModelUtils,
|
|
248
|
+
) -> SuccessData[BaseLiquidHandlingResult] | DefinedErrorData[OverpressureError]:
|
|
249
|
+
"""Execute an aspirate while tracking microoperation."""
|
|
250
|
+
try:
|
|
251
|
+
volume_aspirated = await pipetting.aspirate_while_tracking(
|
|
252
|
+
pipette_id=pipette_id,
|
|
253
|
+
labware_id=labware_id,
|
|
254
|
+
well_name=well_name,
|
|
255
|
+
volume=volume,
|
|
256
|
+
flow_rate=flow_rate,
|
|
257
|
+
command_note_adder=command_note_adder,
|
|
191
258
|
)
|
|
192
259
|
except PipetteOverpressureError as e:
|
|
193
260
|
return DefinedErrorData(
|
|
@@ -217,22 +284,95 @@ async def aspirate_in_place(
|
|
|
217
284
|
)
|
|
218
285
|
|
|
219
286
|
|
|
287
|
+
async def dispense_while_tracking(
|
|
288
|
+
pipette_id: str,
|
|
289
|
+
labware_id: str,
|
|
290
|
+
well_name: str,
|
|
291
|
+
volume: float,
|
|
292
|
+
flow_rate: float,
|
|
293
|
+
push_out: float | None,
|
|
294
|
+
location_if_error: ErrorLocationInfo,
|
|
295
|
+
pipetting: PipettingHandler,
|
|
296
|
+
model_utils: ModelUtils,
|
|
297
|
+
) -> SuccessData[BaseLiquidHandlingResult] | DefinedErrorData[OverpressureError]:
|
|
298
|
+
"""Execute an dispense while tracking microoperation."""
|
|
299
|
+
# The current volume won't be none since it passed validation
|
|
300
|
+
current_volume = (
|
|
301
|
+
pipetting.get_state_view().pipettes.get_aspirated_volume(pipette_id) or 0.0
|
|
302
|
+
)
|
|
303
|
+
is_full_dispense = bool(numpy.isclose(current_volume - volume, 0))
|
|
304
|
+
ready = push_out == 0 if push_out is not None else not is_full_dispense
|
|
305
|
+
try:
|
|
306
|
+
volume_dispensed = await pipetting.dispense_while_tracking(
|
|
307
|
+
pipette_id=pipette_id,
|
|
308
|
+
labware_id=labware_id,
|
|
309
|
+
well_name=well_name,
|
|
310
|
+
volume=volume,
|
|
311
|
+
flow_rate=flow_rate,
|
|
312
|
+
push_out=push_out,
|
|
313
|
+
is_full_dispense=is_full_dispense,
|
|
314
|
+
)
|
|
315
|
+
except PipetteOverpressureError as e:
|
|
316
|
+
return DefinedErrorData(
|
|
317
|
+
public=OverpressureError(
|
|
318
|
+
id=model_utils.generate_id(),
|
|
319
|
+
createdAt=model_utils.get_timestamp(),
|
|
320
|
+
wrappedErrors=[
|
|
321
|
+
ErrorOccurrence.from_failed(
|
|
322
|
+
id=model_utils.generate_id(),
|
|
323
|
+
createdAt=model_utils.get_timestamp(),
|
|
324
|
+
error=e,
|
|
325
|
+
)
|
|
326
|
+
],
|
|
327
|
+
errorInfo=location_if_error,
|
|
328
|
+
),
|
|
329
|
+
state_update=StateUpdate()
|
|
330
|
+
.set_fluid_unknown(pipette_id=pipette_id)
|
|
331
|
+
.set_pipette_ready_to_aspirate(
|
|
332
|
+
pipette_id=pipette_id, ready_to_aspirate=False
|
|
333
|
+
),
|
|
334
|
+
)
|
|
335
|
+
else:
|
|
336
|
+
return SuccessData(
|
|
337
|
+
public=BaseLiquidHandlingResult(
|
|
338
|
+
volume=volume_dispensed,
|
|
339
|
+
),
|
|
340
|
+
state_update=StateUpdate()
|
|
341
|
+
.set_fluid_ejected(
|
|
342
|
+
pipette_id=pipette_id,
|
|
343
|
+
volume=volume_dispensed,
|
|
344
|
+
)
|
|
345
|
+
.set_pipette_ready_to_aspirate(
|
|
346
|
+
pipette_id=pipette_id, ready_to_aspirate=ready
|
|
347
|
+
),
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
220
351
|
async def dispense_in_place(
|
|
221
352
|
pipette_id: str,
|
|
222
353
|
volume: float,
|
|
223
354
|
flow_rate: float,
|
|
224
355
|
push_out: float | None,
|
|
356
|
+
correction_volume: float,
|
|
225
357
|
location_if_error: ErrorLocationInfo,
|
|
226
358
|
pipetting: PipettingHandler,
|
|
227
359
|
model_utils: ModelUtils,
|
|
228
360
|
) -> SuccessData[BaseLiquidHandlingResult] | DefinedErrorData[OverpressureError]:
|
|
229
|
-
"""Dispense-in-place as a
|
|
361
|
+
"""Dispense-in-place as a micro-operation."""
|
|
362
|
+
# The current volume won't be none since it passed validation
|
|
363
|
+
current_volume = (
|
|
364
|
+
pipetting.get_state_view().pipettes.get_aspirated_volume(pipette_id) or 0.0
|
|
365
|
+
)
|
|
366
|
+
is_full_dispense = bool(numpy.isclose(current_volume - volume, 0))
|
|
367
|
+
ready: bool = push_out == 0 if push_out is not None else not is_full_dispense
|
|
230
368
|
try:
|
|
231
369
|
volume = await pipetting.dispense_in_place(
|
|
232
370
|
pipette_id=pipette_id,
|
|
233
371
|
volume=volume,
|
|
234
372
|
flow_rate=flow_rate,
|
|
235
373
|
push_out=push_out,
|
|
374
|
+
is_full_dispense=is_full_dispense,
|
|
375
|
+
correction_volume=correction_volume,
|
|
236
376
|
)
|
|
237
377
|
except PipetteOverpressureError as e:
|
|
238
378
|
return DefinedErrorData(
|
|
@@ -248,13 +388,19 @@ async def dispense_in_place(
|
|
|
248
388
|
],
|
|
249
389
|
errorInfo=location_if_error,
|
|
250
390
|
),
|
|
251
|
-
state_update=StateUpdate()
|
|
391
|
+
state_update=StateUpdate()
|
|
392
|
+
.set_fluid_unknown(pipette_id=pipette_id)
|
|
393
|
+
.set_pipette_ready_to_aspirate(
|
|
394
|
+
pipette_id=pipette_id, ready_to_aspirate=False
|
|
395
|
+
),
|
|
252
396
|
)
|
|
253
397
|
else:
|
|
254
398
|
return SuccessData(
|
|
255
399
|
public=BaseLiquidHandlingResult(volume=volume),
|
|
256
|
-
state_update=StateUpdate()
|
|
257
|
-
|
|
400
|
+
state_update=StateUpdate()
|
|
401
|
+
.set_fluid_ejected(pipette_id=pipette_id, volume=volume)
|
|
402
|
+
.set_pipette_ready_to_aspirate(
|
|
403
|
+
pipette_id=pipette_id, ready_to_aspirate=ready
|
|
258
404
|
),
|
|
259
405
|
)
|
|
260
406
|
|
|
@@ -290,3 +436,8 @@ async def blow_out_in_place(
|
|
|
290
436
|
public=EmptyResult(),
|
|
291
437
|
state_update=StateUpdate().set_fluid_empty(pipette_id=pipette_id),
|
|
292
438
|
)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
async def increase_evo_disp_count(pipette_id: str, pipetting: PipettingHandler) -> None:
|
|
442
|
+
"""Tell a pipette to increase it's evo-tip-dispense-count in eeprom."""
|
|
443
|
+
await pipetting.increase_evo_disp_count(pipette_id)
|
|
@@ -5,7 +5,11 @@ from pydantic import BaseModel
|
|
|
5
5
|
from typing import TYPE_CHECKING, Optional, Type, Union
|
|
6
6
|
from typing_extensions import Literal
|
|
7
7
|
|
|
8
|
-
from .pipetting_common import
|
|
8
|
+
from .pipetting_common import (
|
|
9
|
+
OverpressureError,
|
|
10
|
+
PipetteIdMixin,
|
|
11
|
+
prepare_for_aspirate,
|
|
12
|
+
)
|
|
9
13
|
from .command import (
|
|
10
14
|
AbstractCommandImpl,
|
|
11
15
|
BaseCommand,
|
|
@@ -66,6 +70,14 @@ class PrepareToAspirateImplementation(
|
|
|
66
70
|
|
|
67
71
|
async def execute(self, params: PrepareToAspirateParams) -> _ExecuteReturn:
|
|
68
72
|
"""Prepare the pipette to aspirate."""
|
|
73
|
+
ready_to_aspirate = self._pipetting_handler.get_is_ready_to_aspirate(
|
|
74
|
+
pipette_id=params.pipetteId
|
|
75
|
+
)
|
|
76
|
+
if ready_to_aspirate:
|
|
77
|
+
return SuccessData(
|
|
78
|
+
public=PrepareToAspirateResult(),
|
|
79
|
+
)
|
|
80
|
+
|
|
69
81
|
current_position = await self._gantry_mover.get_position(params.pipetteId)
|
|
70
82
|
prepare_result = await prepare_for_aspirate(
|
|
71
83
|
pipette_id=params.pipetteId,
|
|
@@ -79,13 +91,11 @@ class PrepareToAspirateImplementation(
|
|
|
79
91
|
)
|
|
80
92
|
},
|
|
81
93
|
)
|
|
94
|
+
|
|
82
95
|
if isinstance(prepare_result, DefinedErrorData):
|
|
83
96
|
return prepare_result
|
|
84
97
|
else:
|
|
85
|
-
return
|
|
86
|
-
public=PrepareToAspirateResult(),
|
|
87
|
-
state_update=prepare_result.state_update,
|
|
88
|
-
)
|
|
98
|
+
return self._transform_result(prepare_result)
|
|
89
99
|
|
|
90
100
|
|
|
91
101
|
class PrepareToAspirate(
|