opentrons 8.2.0a3__py2.py3-none-any.whl → 8.3.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.
- opentrons/calibration_storage/deck_configuration.py +3 -3
- opentrons/calibration_storage/file_operators.py +3 -3
- opentrons/calibration_storage/helpers.py +3 -1
- opentrons/calibration_storage/ot2/models/v1.py +16 -29
- opentrons/calibration_storage/ot2/tip_length.py +7 -4
- opentrons/calibration_storage/ot3/models/v1.py +14 -23
- opentrons/cli/analyze.py +18 -6
- opentrons/config/defaults_ot3.py +1 -0
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/errors.py +16 -3
- opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
- opentrons/drivers/command_builder.py +2 -2
- opentrons/drivers/flex_stacker/__init__.py +9 -0
- opentrons/drivers/flex_stacker/abstract.py +89 -0
- opentrons/drivers/flex_stacker/driver.py +260 -0
- opentrons/drivers/flex_stacker/simulator.py +109 -0
- opentrons/drivers/flex_stacker/types.py +138 -0
- opentrons/drivers/heater_shaker/driver.py +18 -3
- opentrons/drivers/temp_deck/driver.py +13 -3
- opentrons/drivers/thermocycler/driver.py +17 -3
- opentrons/execute.py +3 -1
- opentrons/hardware_control/__init__.py +1 -2
- opentrons/hardware_control/api.py +33 -21
- opentrons/hardware_control/backends/flex_protocol.py +17 -7
- opentrons/hardware_control/backends/ot3controller.py +213 -63
- opentrons/hardware_control/backends/ot3simulator.py +18 -9
- opentrons/hardware_control/backends/ot3utils.py +43 -15
- opentrons/hardware_control/dev_types.py +4 -0
- opentrons/hardware_control/emulation/heater_shaker.py +4 -0
- opentrons/hardware_control/emulation/module_server/client.py +1 -1
- opentrons/hardware_control/emulation/module_server/server.py +5 -3
- opentrons/hardware_control/emulation/settings.py +3 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
- opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
- opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
- opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
- opentrons/hardware_control/modules/mod_abc.py +2 -2
- opentrons/hardware_control/motion_utilities.py +68 -0
- opentrons/hardware_control/nozzle_manager.py +39 -41
- opentrons/hardware_control/ot3_calibration.py +1 -1
- opentrons/hardware_control/ot3api.py +78 -31
- opentrons/hardware_control/protocols/gripper_controller.py +3 -0
- opentrons/hardware_control/protocols/hardware_manager.py +5 -1
- opentrons/hardware_control/protocols/liquid_handler.py +22 -1
- opentrons/hardware_control/protocols/motion_controller.py +7 -0
- opentrons/hardware_control/robot_calibration.py +1 -1
- opentrons/hardware_control/types.py +61 -0
- opentrons/legacy_commands/commands.py +37 -0
- opentrons/legacy_commands/types.py +39 -0
- opentrons/protocol_api/__init__.py +20 -1
- opentrons/protocol_api/_liquid.py +24 -49
- opentrons/protocol_api/_liquid_properties.py +754 -0
- opentrons/protocol_api/_types.py +24 -0
- opentrons/protocol_api/core/common.py +2 -0
- opentrons/protocol_api/core/engine/instrument.py +191 -10
- opentrons/protocol_api/core/engine/labware.py +29 -7
- opentrons/protocol_api/core/engine/protocol.py +130 -5
- opentrons/protocol_api/core/engine/robot.py +139 -0
- opentrons/protocol_api/core/engine/well.py +4 -1
- opentrons/protocol_api/core/instrument.py +73 -4
- opentrons/protocol_api/core/labware.py +13 -4
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
- opentrons/protocol_api/core/protocol.py +34 -1
- opentrons/protocol_api/core/robot.py +51 -0
- opentrons/protocol_api/instrument_context.py +299 -44
- opentrons/protocol_api/labware.py +248 -9
- opentrons/protocol_api/module_contexts.py +21 -17
- opentrons/protocol_api/protocol_context.py +125 -4
- opentrons/protocol_api/robot_context.py +204 -32
- opentrons/protocol_api/validation.py +262 -3
- opentrons/protocol_engine/__init__.py +4 -0
- opentrons/protocol_engine/actions/actions.py +2 -3
- opentrons/protocol_engine/clients/sync_client.py +18 -0
- opentrons/protocol_engine/commands/__init__.py +121 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
- opentrons/protocol_engine/commands/absorbance_reader/read.py +40 -10
- opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
- opentrons/protocol_engine/commands/aspirate.py +103 -53
- opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
- opentrons/protocol_engine/commands/blow_out.py +44 -39
- opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
- opentrons/protocol_engine/commands/command.py +73 -66
- opentrons/protocol_engine/commands/command_unions.py +140 -1
- opentrons/protocol_engine/commands/comment.py +1 -1
- opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
- opentrons/protocol_engine/commands/custom.py +6 -12
- opentrons/protocol_engine/commands/dispense.py +82 -48
- opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
- opentrons/protocol_engine/commands/drop_tip.py +52 -31
- opentrons/protocol_engine/commands/drop_tip_in_place.py +79 -8
- opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
- opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
- opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
- opentrons/protocol_engine/commands/get_next_tip.py +134 -0
- opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
- opentrons/protocol_engine/commands/home.py +13 -4
- opentrons/protocol_engine/commands/liquid_probe.py +125 -31
- opentrons/protocol_engine/commands/load_labware.py +33 -6
- opentrons/protocol_engine/commands/load_lid.py +146 -0
- opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
- opentrons/protocol_engine/commands/load_liquid.py +12 -4
- opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
- opentrons/protocol_engine/commands/load_module.py +31 -10
- opentrons/protocol_engine/commands/load_pipette.py +19 -8
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
- opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
- opentrons/protocol_engine/commands/move_labware.py +28 -6
- opentrons/protocol_engine/commands/move_relative.py +35 -25
- opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
- opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
- opentrons/protocol_engine/commands/move_to_well.py +40 -24
- opentrons/protocol_engine/commands/movement_common.py +338 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
- opentrons/protocol_engine/commands/pipetting_common.py +169 -87
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
- opentrons/protocol_engine/commands/reload_labware.py +1 -1
- opentrons/protocol_engine/commands/retract_axis.py +1 -1
- opentrons/protocol_engine/commands/robot/__init__.py +69 -0
- opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
- opentrons/protocol_engine/commands/robot/common.py +18 -0
- opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
- opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
- opentrons/protocol_engine/commands/robot/move_to.py +94 -0
- opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
- opentrons/protocol_engine/commands/save_position.py +14 -5
- opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
- opentrons/protocol_engine/commands/set_status_bar.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +9 -3
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
- opentrons/protocol_engine/commands/touch_tip.py +65 -16
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +5 -2
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +13 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +2 -5
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +4 -2
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +2 -5
- opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
- opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
- opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
- opentrons/protocol_engine/errors/__init__.py +12 -0
- opentrons/protocol_engine/errors/error_occurrence.py +19 -20
- opentrons/protocol_engine/errors/exceptions.py +76 -0
- opentrons/protocol_engine/execution/command_executor.py +1 -1
- opentrons/protocol_engine/execution/equipment.py +73 -5
- opentrons/protocol_engine/execution/gantry_mover.py +369 -8
- opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
- opentrons/protocol_engine/execution/movement.py +27 -0
- opentrons/protocol_engine/execution/pipetting.py +5 -1
- opentrons/protocol_engine/execution/tip_handler.py +34 -15
- opentrons/protocol_engine/notes/notes.py +1 -1
- opentrons/protocol_engine/protocol_engine.py +7 -6
- opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_validation.py +18 -0
- opentrons/protocol_engine/resources/module_data_provider.py +1 -1
- opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
- opentrons/protocol_engine/slot_standardization.py +9 -9
- opentrons/protocol_engine/state/_move_types.py +9 -5
- opentrons/protocol_engine/state/_well_math.py +193 -0
- opentrons/protocol_engine/state/addressable_areas.py +25 -61
- opentrons/protocol_engine/state/command_history.py +12 -0
- opentrons/protocol_engine/state/commands.py +22 -14
- opentrons/protocol_engine/state/files.py +10 -12
- opentrons/protocol_engine/state/fluid_stack.py +138 -0
- opentrons/protocol_engine/state/frustum_helpers.py +63 -69
- opentrons/protocol_engine/state/geometry.py +47 -1
- opentrons/protocol_engine/state/labware.py +92 -26
- opentrons/protocol_engine/state/liquid_classes.py +82 -0
- opentrons/protocol_engine/state/liquids.py +16 -4
- opentrons/protocol_engine/state/modules.py +56 -71
- opentrons/protocol_engine/state/motion.py +6 -1
- opentrons/protocol_engine/state/pipettes.py +149 -58
- opentrons/protocol_engine/state/state.py +21 -2
- opentrons/protocol_engine/state/state_summary.py +4 -2
- opentrons/protocol_engine/state/tips.py +11 -44
- opentrons/protocol_engine/state/update_types.py +343 -48
- opentrons/protocol_engine/state/wells.py +19 -11
- opentrons/protocol_engine/types.py +176 -28
- opentrons/protocol_reader/extract_labware_definitions.py +5 -2
- opentrons/protocol_reader/file_format_validator.py +5 -5
- opentrons/protocol_runner/json_file_reader.py +9 -3
- opentrons/protocol_runner/json_translator.py +51 -25
- opentrons/protocol_runner/legacy_command_mapper.py +66 -64
- opentrons/protocol_runner/protocol_runner.py +35 -4
- opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
- opentrons/protocol_runner/run_orchestrator.py +13 -3
- opentrons/protocols/advanced_control/common.py +38 -0
- opentrons/protocols/advanced_control/mix.py +1 -1
- opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
- opentrons/protocols/advanced_control/transfers/common.py +56 -0
- opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +1 -1
- opentrons/protocols/api_support/util.py +10 -0
- opentrons/protocols/labware.py +70 -8
- opentrons/protocols/models/json_protocol.py +5 -9
- opentrons/simulate.py +3 -1
- opentrons/types.py +162 -2
- opentrons/util/entrypoint_util.py +2 -5
- opentrons/util/logging_config.py +1 -1
- {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
- {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
- {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
- {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
- {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Basic addressable area data state and store."""
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from functools import cached_property
|
|
4
|
-
from typing import Dict, List, Optional, Set
|
|
4
|
+
from typing import Dict, List, Optional, Set
|
|
5
5
|
|
|
6
6
|
from opentrons_shared_data.robot.types import RobotType, RobotDefinition
|
|
7
7
|
from opentrons_shared_data.deck.types import (
|
|
@@ -12,14 +12,6 @@ from opentrons_shared_data.deck.types import (
|
|
|
12
12
|
|
|
13
13
|
from opentrons.types import Point, DeckSlotName
|
|
14
14
|
|
|
15
|
-
from ..commands import (
|
|
16
|
-
Command,
|
|
17
|
-
LoadLabwareResult,
|
|
18
|
-
LoadModuleResult,
|
|
19
|
-
MoveLabwareResult,
|
|
20
|
-
MoveToAddressableAreaResult,
|
|
21
|
-
MoveToAddressableAreaForDropTipResult,
|
|
22
|
-
)
|
|
23
15
|
from ..errors import (
|
|
24
16
|
IncompatibleAddressableAreaError,
|
|
25
17
|
AreaNotInDeckConfigurationError,
|
|
@@ -29,19 +21,18 @@ from ..errors import (
|
|
|
29
21
|
)
|
|
30
22
|
from ..resources import deck_configuration_provider
|
|
31
23
|
from ..types import (
|
|
32
|
-
DeckSlotLocation,
|
|
33
|
-
AddressableAreaLocation,
|
|
34
24
|
AddressableArea,
|
|
35
25
|
PotentialCutoutFixture,
|
|
36
26
|
DeckConfigurationType,
|
|
37
27
|
Dimensions,
|
|
38
28
|
)
|
|
29
|
+
from ..actions.get_state_update import get_state_updates
|
|
39
30
|
from ..actions import (
|
|
40
31
|
Action,
|
|
41
|
-
SucceedCommandAction,
|
|
42
32
|
SetDeckConfigurationAction,
|
|
43
33
|
AddAddressableAreaAction,
|
|
44
34
|
)
|
|
35
|
+
from . import update_types
|
|
45
36
|
from .config import Config
|
|
46
37
|
from ._abstract_store import HasState, HandlesActions
|
|
47
38
|
|
|
@@ -193,10 +184,14 @@ class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
|
|
|
193
184
|
|
|
194
185
|
def handle_action(self, action: Action) -> None:
|
|
195
186
|
"""Modify state in reaction to an action."""
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
187
|
+
for state_update in get_state_updates(action):
|
|
188
|
+
if state_update.addressable_area_used != update_types.NO_CHANGE:
|
|
189
|
+
self._add_addressable_area(
|
|
190
|
+
state_update.addressable_area_used.addressable_area_name
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if isinstance(action, AddAddressableAreaAction):
|
|
194
|
+
self._add_addressable_area(action.addressable_area_name)
|
|
200
195
|
elif isinstance(action, SetDeckConfigurationAction):
|
|
201
196
|
current_state = self._state
|
|
202
197
|
if (
|
|
@@ -211,28 +206,6 @@ class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
|
|
|
211
206
|
)
|
|
212
207
|
)
|
|
213
208
|
|
|
214
|
-
def _handle_command(self, command: Command) -> None:
|
|
215
|
-
"""Modify state in reaction to a command."""
|
|
216
|
-
if isinstance(command.result, LoadLabwareResult):
|
|
217
|
-
location = command.params.location
|
|
218
|
-
if isinstance(location, (DeckSlotLocation, AddressableAreaLocation)):
|
|
219
|
-
self._check_location_is_addressable_area(location)
|
|
220
|
-
|
|
221
|
-
elif isinstance(command.result, MoveLabwareResult):
|
|
222
|
-
location = command.params.newLocation
|
|
223
|
-
if isinstance(location, (DeckSlotLocation, AddressableAreaLocation)):
|
|
224
|
-
self._check_location_is_addressable_area(location)
|
|
225
|
-
|
|
226
|
-
elif isinstance(command.result, LoadModuleResult):
|
|
227
|
-
self._check_location_is_addressable_area(command.params.location)
|
|
228
|
-
|
|
229
|
-
elif isinstance(
|
|
230
|
-
command.result,
|
|
231
|
-
(MoveToAddressableAreaResult, MoveToAddressableAreaForDropTipResult),
|
|
232
|
-
):
|
|
233
|
-
addressable_area_name = command.params.addressableAreaName
|
|
234
|
-
self._check_location_is_addressable_area(addressable_area_name)
|
|
235
|
-
|
|
236
209
|
@staticmethod
|
|
237
210
|
def _get_addressable_areas_from_deck_configuration(
|
|
238
211
|
deck_config: DeckConfigurationType, deck_definition: DeckDefinitionV5
|
|
@@ -260,16 +233,7 @@ class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
|
|
|
260
233
|
)
|
|
261
234
|
return {area.area_name: area for area in addressable_areas}
|
|
262
235
|
|
|
263
|
-
def
|
|
264
|
-
self, location: Union[DeckSlotLocation, AddressableAreaLocation, str]
|
|
265
|
-
) -> None:
|
|
266
|
-
if isinstance(location, DeckSlotLocation):
|
|
267
|
-
addressable_area_name = location.slotName.id
|
|
268
|
-
elif isinstance(location, AddressableAreaLocation):
|
|
269
|
-
addressable_area_name = location.addressableAreaName
|
|
270
|
-
else:
|
|
271
|
-
addressable_area_name = location
|
|
272
|
-
|
|
236
|
+
def _add_addressable_area(self, addressable_area_name: str) -> None:
|
|
273
237
|
if addressable_area_name not in self._state.loaded_addressable_areas_by_name:
|
|
274
238
|
cutout_id = self._validate_addressable_area_for_simulation(
|
|
275
239
|
addressable_area_name
|
|
@@ -323,7 +287,7 @@ class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
|
|
|
323
287
|
return cutout_id
|
|
324
288
|
|
|
325
289
|
|
|
326
|
-
class AddressableAreaView
|
|
290
|
+
class AddressableAreaView:
|
|
327
291
|
"""Read-only addressable area state view."""
|
|
328
292
|
|
|
329
293
|
_state: AddressableAreaState
|
|
@@ -345,8 +309,8 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
345
309
|
@cached_property
|
|
346
310
|
def mount_offsets(self) -> Dict[str, Point]:
|
|
347
311
|
"""The left and right mount offsets of the robot."""
|
|
348
|
-
left_offset = self.
|
|
349
|
-
right_offset = self.
|
|
312
|
+
left_offset = self._state.robot_definition["mountOffsets"]["left"]
|
|
313
|
+
right_offset = self._state.robot_definition["mountOffsets"]["right"]
|
|
350
314
|
return {
|
|
351
315
|
"left": Point(x=left_offset[0], y=left_offset[1], z=left_offset[2]),
|
|
352
316
|
"right": Point(x=right_offset[0], y=right_offset[1], z=right_offset[2]),
|
|
@@ -355,10 +319,10 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
355
319
|
@cached_property
|
|
356
320
|
def padding_offsets(self) -> Dict[str, float]:
|
|
357
321
|
"""The padding offsets to be applied to the deck extents of the robot."""
|
|
358
|
-
rear_offset = self.
|
|
359
|
-
front_offset = self.
|
|
360
|
-
left_side_offset = self.
|
|
361
|
-
right_side_offset = self.
|
|
322
|
+
rear_offset = self._state.robot_definition["paddingOffsets"]["rear"]
|
|
323
|
+
front_offset = self._state.robot_definition["paddingOffsets"]["front"]
|
|
324
|
+
left_side_offset = self._state.robot_definition["paddingOffsets"]["leftSide"]
|
|
325
|
+
right_side_offset = self._state.robot_definition["paddingOffsets"]["rightSide"]
|
|
362
326
|
return {
|
|
363
327
|
"rear": rear_offset,
|
|
364
328
|
"front": front_offset,
|
|
@@ -420,12 +384,12 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
420
384
|
_get_conflicting_addressable_areas_error_string(
|
|
421
385
|
self._state.potential_cutout_fixtures_by_cutout_id[cutout_id],
|
|
422
386
|
self._state.loaded_addressable_areas_by_name,
|
|
423
|
-
self.
|
|
387
|
+
self._state.deck_definition,
|
|
424
388
|
)
|
|
425
389
|
)
|
|
426
390
|
area_display_name = (
|
|
427
391
|
deck_configuration_provider.get_addressable_area_display_name(
|
|
428
|
-
area_name, self.
|
|
392
|
+
area_name, self._state.deck_definition
|
|
429
393
|
)
|
|
430
394
|
)
|
|
431
395
|
raise IncompatibleAddressableAreaError(
|
|
@@ -504,7 +468,7 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
504
468
|
addressable_area_name: str,
|
|
505
469
|
) -> Point:
|
|
506
470
|
"""Get the offset form cutout fixture of an addressable area."""
|
|
507
|
-
for addressable_area in self.
|
|
471
|
+
for addressable_area in self._state.deck_definition["locations"][
|
|
508
472
|
"addressableAreas"
|
|
509
473
|
]:
|
|
510
474
|
if addressable_area["id"] == addressable_area_name:
|
|
@@ -568,7 +532,7 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
568
532
|
self, slot_name: DeckSlotName
|
|
569
533
|
) -> Optional[CutoutFixture]:
|
|
570
534
|
"""Get the Cutout Fixture currently loaded where a specific Deck Slot would be."""
|
|
571
|
-
deck_config = self.
|
|
535
|
+
deck_config = self._state.deck_configuration
|
|
572
536
|
if deck_config:
|
|
573
537
|
slot_cutout_id = DECK_SLOT_TO_CUTOUT_MAP[slot_name]
|
|
574
538
|
slot_cutout_fixture = None
|
|
@@ -581,7 +545,7 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
581
545
|
if cutout_id == slot_cutout_id:
|
|
582
546
|
slot_cutout_fixture = (
|
|
583
547
|
deck_configuration_provider.get_cutout_fixture(
|
|
584
|
-
cutout_fixture_id, self.
|
|
548
|
+
cutout_fixture_id, self._state.deck_definition
|
|
585
549
|
)
|
|
586
550
|
)
|
|
587
551
|
return slot_cutout_fixture
|
|
@@ -605,7 +569,7 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
605
569
|
self, slot_name: DeckSlotName
|
|
606
570
|
) -> Optional[str]:
|
|
607
571
|
"""Get the serial number provided by the deck configuration for a Fixture at a given location."""
|
|
608
|
-
deck_config = self.
|
|
572
|
+
deck_config = self._state.deck_configuration
|
|
609
573
|
if deck_config:
|
|
610
574
|
slot_cutout_id = DECK_SLOT_TO_CUTOUT_MAP[slot_name]
|
|
611
575
|
# This will only ever be one under current assumptions
|
|
@@ -24,6 +24,9 @@ class CommandHistory:
|
|
|
24
24
|
_all_command_ids: List[str]
|
|
25
25
|
"""All command IDs, in insertion order."""
|
|
26
26
|
|
|
27
|
+
_all_failed_command_ids: List[str]
|
|
28
|
+
"""All failed command IDs, in insertion order."""
|
|
29
|
+
|
|
27
30
|
_all_command_ids_but_fixit_command_ids: List[str]
|
|
28
31
|
"""All command IDs besides fixit command intents, in insertion order."""
|
|
29
32
|
|
|
@@ -47,6 +50,7 @@ class CommandHistory:
|
|
|
47
50
|
|
|
48
51
|
def __init__(self) -> None:
|
|
49
52
|
self._all_command_ids = []
|
|
53
|
+
self._all_failed_command_ids = []
|
|
50
54
|
self._all_command_ids_but_fixit_command_ids = []
|
|
51
55
|
self._queued_command_ids = OrderedSet()
|
|
52
56
|
self._queued_setup_command_ids = OrderedSet()
|
|
@@ -101,6 +105,13 @@ class CommandHistory:
|
|
|
101
105
|
for command_id in self._all_command_ids
|
|
102
106
|
]
|
|
103
107
|
|
|
108
|
+
def get_all_failed_commands(self) -> List[Command]:
|
|
109
|
+
"""Get all failed commands."""
|
|
110
|
+
return [
|
|
111
|
+
self._commands_by_id[command_id].command
|
|
112
|
+
for command_id in self._all_failed_command_ids
|
|
113
|
+
]
|
|
114
|
+
|
|
104
115
|
def get_filtered_command_ids(self, include_fixit_commands: bool) -> List[str]:
|
|
105
116
|
"""Get all fixit command IDs."""
|
|
106
117
|
if include_fixit_commands:
|
|
@@ -242,6 +253,7 @@ class CommandHistory:
|
|
|
242
253
|
self._remove_queue_id(command.id)
|
|
243
254
|
self._remove_setup_queue_id(command.id)
|
|
244
255
|
self._set_most_recently_completed_command_id(command.id)
|
|
256
|
+
self._all_failed_command_ids.append(command.id)
|
|
245
257
|
|
|
246
258
|
def _add(self, command_id: str, command_entry: CommandEntry) -> None:
|
|
247
259
|
"""Create or update a command entry."""
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Protocol engine commands sub-state."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import enum
|
|
@@ -228,9 +229,6 @@ class CommandState:
|
|
|
228
229
|
This value can be used to generate future hashes.
|
|
229
230
|
"""
|
|
230
231
|
|
|
231
|
-
failed_command_errors: List[ErrorOccurrence]
|
|
232
|
-
"""List of command errors that occurred during run execution."""
|
|
233
|
-
|
|
234
232
|
has_entered_error_recovery: bool
|
|
235
233
|
"""Whether the run has entered error recovery."""
|
|
236
234
|
|
|
@@ -269,7 +267,6 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
269
267
|
run_started_at=None,
|
|
270
268
|
latest_protocol_command_hash=None,
|
|
271
269
|
stopped_by_estop=False,
|
|
272
|
-
failed_command_errors=[],
|
|
273
270
|
error_recovery_policy=error_recovery_policy,
|
|
274
271
|
has_entered_error_recovery=False,
|
|
275
272
|
)
|
|
@@ -308,7 +305,7 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
308
305
|
# TODO(mc, 2021-06-22): mypy has trouble with this automatic
|
|
309
306
|
# request > command mapping, figure out how to type precisely
|
|
310
307
|
# (or wait for a future mypy version that can figure it out).
|
|
311
|
-
queued_command = action.request._CommandCls.
|
|
308
|
+
queued_command = action.request._CommandCls.model_construct(
|
|
312
309
|
id=action.command_id,
|
|
313
310
|
key=(
|
|
314
311
|
action.request.key
|
|
@@ -330,7 +327,7 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
330
327
|
def _handle_run_command_action(self, action: RunCommandAction) -> None:
|
|
331
328
|
prev_entry = self._state.command_history.get(action.command_id)
|
|
332
329
|
|
|
333
|
-
running_command = prev_entry.command.
|
|
330
|
+
running_command = prev_entry.command.model_copy(
|
|
334
331
|
update={
|
|
335
332
|
"status": CommandStatus.RUNNING,
|
|
336
333
|
"startedAt": action.started_at,
|
|
@@ -366,7 +363,6 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
366
363
|
notes=action.notes,
|
|
367
364
|
)
|
|
368
365
|
self._state.failed_command = self._state.command_history.get(action.command_id)
|
|
369
|
-
self._state.failed_command_errors.append(public_error_occurrence)
|
|
370
366
|
|
|
371
367
|
if (
|
|
372
368
|
prev_entry.command.intent in (CommandIntent.PROTOCOL, None)
|
|
@@ -511,7 +507,10 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
511
507
|
pass
|
|
512
508
|
case QueueStatus.RUNNING | QueueStatus.PAUSED:
|
|
513
509
|
self._state.queue_status = QueueStatus.PAUSED
|
|
514
|
-
case
|
|
510
|
+
case (
|
|
511
|
+
QueueStatus.AWAITING_RECOVERY
|
|
512
|
+
| QueueStatus.AWAITING_RECOVERY_PAUSED
|
|
513
|
+
):
|
|
515
514
|
self._state.queue_status = QueueStatus.AWAITING_RECOVERY_PAUSED
|
|
516
515
|
elif action.door_state == DoorState.CLOSED:
|
|
517
516
|
self._state.is_door_blocking = False
|
|
@@ -530,7 +529,7 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
530
529
|
notes: Optional[List[CommandNote]],
|
|
531
530
|
) -> None:
|
|
532
531
|
prev_entry = self._state.command_history.get(command_id)
|
|
533
|
-
failed_command = prev_entry.command.
|
|
532
|
+
failed_command = prev_entry.command.model_copy(
|
|
534
533
|
update={
|
|
535
534
|
"completedAt": failed_at,
|
|
536
535
|
"status": CommandStatus.FAILED,
|
|
@@ -584,7 +583,7 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
584
583
|
)
|
|
585
584
|
|
|
586
585
|
|
|
587
|
-
class CommandView
|
|
586
|
+
class CommandView:
|
|
588
587
|
"""Read-only command state view."""
|
|
589
588
|
|
|
590
589
|
_state: CommandState
|
|
@@ -684,7 +683,7 @@ class CommandView(HasState[CommandState]):
|
|
|
684
683
|
finish_error = self._state.finish_error
|
|
685
684
|
|
|
686
685
|
if run_error and finish_error:
|
|
687
|
-
combined_error = ErrorOccurrence
|
|
686
|
+
combined_error = ErrorOccurrence(
|
|
688
687
|
id=finish_error.id,
|
|
689
688
|
createdAt=finish_error.createdAt,
|
|
690
689
|
errorType="RunAndFinishFailed",
|
|
@@ -706,7 +705,12 @@ class CommandView(HasState[CommandState]):
|
|
|
706
705
|
|
|
707
706
|
def get_all_errors(self) -> List[ErrorOccurrence]:
|
|
708
707
|
"""Get the run's full error list, if there was none, returns an empty list."""
|
|
709
|
-
|
|
708
|
+
failed_commands = self._state.command_history.get_all_failed_commands()
|
|
709
|
+
return [
|
|
710
|
+
command_error.error
|
|
711
|
+
for command_error in failed_commands
|
|
712
|
+
if command_error.error is not None
|
|
713
|
+
]
|
|
710
714
|
|
|
711
715
|
def get_has_entered_recovery_mode(self) -> bool:
|
|
712
716
|
"""Get whether the run has entered recovery mode."""
|
|
@@ -916,7 +920,7 @@ class CommandView(HasState[CommandState]):
|
|
|
916
920
|
fatal error of the overall run coming from anywhere in the Python script,
|
|
917
921
|
including in between commands.
|
|
918
922
|
"""
|
|
919
|
-
failed_command = self.
|
|
923
|
+
failed_command = self._state.failed_command
|
|
920
924
|
if (
|
|
921
925
|
failed_command
|
|
922
926
|
and failed_command.command.error
|
|
@@ -932,12 +936,16 @@ class CommandView(HasState[CommandState]):
|
|
|
932
936
|
|
|
933
937
|
The command ID is assumed to point to a failed command.
|
|
934
938
|
"""
|
|
935
|
-
return self.
|
|
939
|
+
return self._state.command_error_recovery_types[command_id]
|
|
936
940
|
|
|
937
941
|
def get_is_stopped(self) -> bool:
|
|
938
942
|
"""Get whether an engine stop has completed."""
|
|
939
943
|
return self._state.run_completed_at is not None
|
|
940
944
|
|
|
945
|
+
def get_is_stopped_by_estop(self) -> bool:
|
|
946
|
+
"""Return whether the engine was stopped specifically by an E-stop."""
|
|
947
|
+
return self._state.stopped_by_estop
|
|
948
|
+
|
|
941
949
|
def has_been_played(self) -> bool:
|
|
942
950
|
"""Get whether engine has started."""
|
|
943
951
|
return self._state.run_started_at is not None
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from typing import List
|
|
4
4
|
|
|
5
|
+
from opentrons.protocol_engine.actions.get_state_update import get_state_updates
|
|
6
|
+
from opentrons.protocol_engine.state import update_types
|
|
7
|
+
|
|
5
8
|
from ._abstract_store import HasState, HandlesActions
|
|
6
|
-
from ..actions import Action
|
|
7
|
-
from ..commands import (
|
|
8
|
-
Command,
|
|
9
|
-
absorbance_reader,
|
|
10
|
-
)
|
|
9
|
+
from ..actions import Action
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
@dataclass
|
|
@@ -28,16 +27,15 @@ class FileStore(HasState[FileState], HandlesActions):
|
|
|
28
27
|
|
|
29
28
|
def handle_action(self, action: Action) -> None:
|
|
30
29
|
"""Modify state in reaction to an action."""
|
|
31
|
-
|
|
32
|
-
self.
|
|
30
|
+
for state_update in get_state_updates(action):
|
|
31
|
+
self._handle_state_update(state_update)
|
|
33
32
|
|
|
34
|
-
def
|
|
35
|
-
if
|
|
36
|
-
|
|
37
|
-
self._state.file_ids.extend(command.result.fileIds)
|
|
33
|
+
def _handle_state_update(self, state_update: update_types.StateUpdate) -> None:
|
|
34
|
+
if state_update.files_added != update_types.NO_CHANGE:
|
|
35
|
+
self._state.file_ids.extend(state_update.files_added.file_ids)
|
|
38
36
|
|
|
39
37
|
|
|
40
|
-
class FileView
|
|
38
|
+
class FileView:
|
|
41
39
|
"""Read-only engine created file state view."""
|
|
42
40
|
|
|
43
41
|
_state: FileState
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Implements fluid stack tracking for pipettes.
|
|
2
|
+
|
|
3
|
+
Inside a pipette's tip, there can be a mix of kinds of fluids - here, "fluid" means "liquid" (i.e. a protocol-relevant
|
|
4
|
+
working liquid that is aspirated or dispensed from wells) or "air" (i.e. because there was an air gap). Since sometimes
|
|
5
|
+
you want air gaps in different places - physically-below liquid to prevent dripping, physically-above liquid to provide
|
|
6
|
+
extra room to push the plunger - we need to support some notion of at least phsyical ordinal position of air and liquid,
|
|
7
|
+
and we do so as a logical stack because that's physically relevant.
|
|
8
|
+
"""
|
|
9
|
+
from logging import getLogger
|
|
10
|
+
from numpy import isclose
|
|
11
|
+
from ..types import AspiratedFluid, FluidKind
|
|
12
|
+
|
|
13
|
+
_LOG = getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FluidStack:
|
|
17
|
+
"""A FluidStack data structure is a list of AspiratedFluids, with stack-style (last-in-first-out) ordering.
|
|
18
|
+
|
|
19
|
+
The front of the list is the physical-top of the liquid stack (logical-bottom of the stack data structure)
|
|
20
|
+
and the back of the list is the physical-bottom of the liquid stack (logical-top of the stack data structure).
|
|
21
|
+
The state is internal and the interaction surface is the methods. This is a mutating API.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
_FluidStack = list[AspiratedFluid]
|
|
25
|
+
|
|
26
|
+
_fluid_stack: _FluidStack
|
|
27
|
+
|
|
28
|
+
def __init__(self, _fluid_stack: _FluidStack | None = None) -> None:
|
|
29
|
+
"""Build a FluidStack.
|
|
30
|
+
|
|
31
|
+
The argument is provided for testing and shouldn't be generally used.
|
|
32
|
+
"""
|
|
33
|
+
self._fluid_stack = _fluid_stack or []
|
|
34
|
+
|
|
35
|
+
def add_fluid(self, new: AspiratedFluid) -> None:
|
|
36
|
+
"""Add fluid to a stack.
|
|
37
|
+
|
|
38
|
+
If the new fluid is of a different kind than what's on the physical-bottom of the stack, add a new record.
|
|
39
|
+
If the new fluid is of the same kind as what's on the physical-bottom of the stack, add the new volume to
|
|
40
|
+
the same record.
|
|
41
|
+
"""
|
|
42
|
+
if len(self._fluid_stack) == 0 or self._fluid_stack[-1].kind != new.kind:
|
|
43
|
+
# this is a new kind of fluid, append the record
|
|
44
|
+
self._fluid_stack.append(new)
|
|
45
|
+
else:
|
|
46
|
+
# this is more of the same kind of fluid, add the volumes
|
|
47
|
+
old_fluid = self._fluid_stack.pop(-1)
|
|
48
|
+
self._fluid_stack.append(
|
|
49
|
+
AspiratedFluid(kind=new.kind, volume=old_fluid.volume + new.volume)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def _alter_fluid_records(
|
|
53
|
+
self, remove: int, new_last: AspiratedFluid | None
|
|
54
|
+
) -> None:
|
|
55
|
+
if remove >= len(self._fluid_stack) or len(self._fluid_stack) == 0:
|
|
56
|
+
self._fluid_stack = []
|
|
57
|
+
return
|
|
58
|
+
if remove != 0:
|
|
59
|
+
removed = self._fluid_stack[:-remove]
|
|
60
|
+
else:
|
|
61
|
+
removed = self._fluid_stack
|
|
62
|
+
if new_last:
|
|
63
|
+
removed[-1] = new_last
|
|
64
|
+
self._fluid_stack = removed
|
|
65
|
+
|
|
66
|
+
def remove_fluid(self, volume: float) -> None:
|
|
67
|
+
"""Remove a specific amount of fluid from the physical-bottom of the stack.
|
|
68
|
+
|
|
69
|
+
This will consume records that are wholly included in the provided volume and alter the remaining
|
|
70
|
+
final records (if any) to decrement the amount of volume removed from it.
|
|
71
|
+
|
|
72
|
+
This function is designed to be used inside pipette store action handlers, which are generally not
|
|
73
|
+
exception-safe, and therefore swallows and logs errors.
|
|
74
|
+
"""
|
|
75
|
+
self._fluid_stack_iterator = reversed(self._fluid_stack)
|
|
76
|
+
removed_elements: list[AspiratedFluid] = []
|
|
77
|
+
while volume > 0:
|
|
78
|
+
try:
|
|
79
|
+
last_stack_element = next(self._fluid_stack_iterator)
|
|
80
|
+
except StopIteration:
|
|
81
|
+
_LOG.error(
|
|
82
|
+
f"Attempting to remove more fluid than present, {volume}uL left over"
|
|
83
|
+
)
|
|
84
|
+
self._alter_fluid_records(len(removed_elements), None)
|
|
85
|
+
return
|
|
86
|
+
if last_stack_element.volume < volume:
|
|
87
|
+
removed_elements.append(last_stack_element)
|
|
88
|
+
volume -= last_stack_element.volume
|
|
89
|
+
elif isclose(last_stack_element.volume, volume):
|
|
90
|
+
self._alter_fluid_records(len(removed_elements) + 1, None)
|
|
91
|
+
return
|
|
92
|
+
else:
|
|
93
|
+
self._alter_fluid_records(
|
|
94
|
+
len(removed_elements),
|
|
95
|
+
AspiratedFluid(
|
|
96
|
+
kind=last_stack_element.kind,
|
|
97
|
+
volume=last_stack_element.volume - volume,
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
_LOG.error(f"Failed to handle removing {volume}uL from {self._fluid_stack}")
|
|
103
|
+
|
|
104
|
+
def aspirated_volume(self, kind: FluidKind | None = None) -> float:
|
|
105
|
+
"""Measure the total amount of fluid (optionally filtered by kind) in the stack."""
|
|
106
|
+
volume = 0.0
|
|
107
|
+
for el in self._fluid_stack:
|
|
108
|
+
if kind is not None and el.kind != kind:
|
|
109
|
+
continue
|
|
110
|
+
volume += el.volume
|
|
111
|
+
return volume
|
|
112
|
+
|
|
113
|
+
def liquid_part_of_dispense_volume(self, volume: float) -> float:
|
|
114
|
+
"""Get the amount of liquid in the specified volume starting at the physical-bottom of the stack."""
|
|
115
|
+
liquid_volume = 0.0
|
|
116
|
+
for el in reversed(self._fluid_stack):
|
|
117
|
+
if el.kind == FluidKind.LIQUID:
|
|
118
|
+
liquid_volume += min(volume, el.volume)
|
|
119
|
+
volume -= min(el.volume, volume)
|
|
120
|
+
if isclose(volume, 0.0):
|
|
121
|
+
return liquid_volume
|
|
122
|
+
return liquid_volume
|
|
123
|
+
|
|
124
|
+
def __eq__(self, other: object) -> bool:
|
|
125
|
+
"""Equality."""
|
|
126
|
+
if isinstance(other, type(self)):
|
|
127
|
+
return other._fluid_stack == self._fluid_stack
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
def __repr__(self) -> str:
|
|
131
|
+
"""String representation of a fluid stack."""
|
|
132
|
+
if self._fluid_stack:
|
|
133
|
+
stringified_stack = (
|
|
134
|
+
f'(top) {", ".join([str(item) for item in self._fluid_stack])} (bottom)'
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
stringified_stack = "empty"
|
|
138
|
+
return f"<{self.__class__.__name__}: {stringified_stack}>"
|