opentrons 8.1.0a0__py2.py3-none-any.whl → 8.2.0a0__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/cli/analyze.py +71 -7
- opentrons/config/__init__.py +9 -0
- opentrons/config/advanced_settings.py +22 -0
- opentrons/config/defaults_ot3.py +14 -36
- opentrons/config/feature_flags.py +4 -0
- opentrons/config/types.py +6 -17
- opentrons/drivers/absorbance_reader/abstract.py +27 -3
- opentrons/drivers/absorbance_reader/async_byonoy.py +207 -154
- opentrons/drivers/absorbance_reader/driver.py +24 -15
- opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
- opentrons/drivers/absorbance_reader/simulator.py +32 -6
- opentrons/drivers/types.py +23 -1
- opentrons/execute.py +2 -2
- opentrons/hardware_control/api.py +18 -10
- opentrons/hardware_control/backends/controller.py +3 -2
- opentrons/hardware_control/backends/flex_protocol.py +11 -5
- opentrons/hardware_control/backends/ot3controller.py +18 -50
- opentrons/hardware_control/backends/ot3simulator.py +7 -6
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
- opentrons/hardware_control/module_control.py +43 -2
- opentrons/hardware_control/modules/__init__.py +7 -1
- opentrons/hardware_control/modules/absorbance_reader.py +230 -83
- opentrons/hardware_control/modules/errors.py +7 -0
- opentrons/hardware_control/modules/heater_shaker.py +8 -3
- opentrons/hardware_control/modules/magdeck.py +12 -3
- opentrons/hardware_control/modules/mod_abc.py +27 -2
- opentrons/hardware_control/modules/tempdeck.py +15 -7
- opentrons/hardware_control/modules/thermocycler.py +69 -3
- opentrons/hardware_control/modules/types.py +11 -5
- opentrons/hardware_control/modules/update.py +11 -5
- opentrons/hardware_control/modules/utils.py +3 -1
- opentrons/hardware_control/ot3_calibration.py +6 -6
- opentrons/hardware_control/ot3api.py +126 -89
- opentrons/hardware_control/poller.py +15 -11
- opentrons/hardware_control/protocols/__init__.py +1 -7
- opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
- opentrons/hardware_control/protocols/liquid_handler.py +5 -0
- opentrons/motion_planning/__init__.py +2 -0
- opentrons/motion_planning/waypoints.py +32 -0
- opentrons/protocol_api/__init__.py +2 -1
- opentrons/protocol_api/_liquid.py +87 -1
- opentrons/protocol_api/_parameter_context.py +10 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
- opentrons/protocol_api/core/engine/instrument.py +29 -25
- opentrons/protocol_api/core/engine/labware.py +10 -2
- opentrons/protocol_api/core/engine/module_core.py +129 -17
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +355 -0
- opentrons/protocol_api/core/engine/protocol.py +55 -2
- opentrons/protocol_api/core/instrument.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +5 -2
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/module.py +22 -4
- opentrons/protocol_api/core/protocol.py +5 -2
- opentrons/protocol_api/instrument_context.py +52 -20
- opentrons/protocol_api/labware.py +13 -1
- opentrons/protocol_api/module_contexts.py +68 -13
- opentrons/protocol_api/protocol_context.py +38 -4
- opentrons/protocol_api/validation.py +5 -3
- opentrons/protocol_engine/__init__.py +10 -9
- opentrons/protocol_engine/actions/__init__.py +5 -0
- opentrons/protocol_engine/actions/actions.py +42 -25
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/sync_client.py +7 -1
- opentrons/protocol_engine/clients/transports.py +1 -1
- opentrons/protocol_engine/commands/__init__.py +0 -4
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +161 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +53 -9
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +160 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +196 -0
- opentrons/protocol_engine/commands/aspirate.py +29 -16
- opentrons/protocol_engine/commands/aspirate_in_place.py +32 -15
- opentrons/protocol_engine/commands/blow_out.py +63 -14
- opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
- opentrons/protocol_engine/commands/command.py +28 -17
- opentrons/protocol_engine/commands/command_unions.py +37 -24
- opentrons/protocol_engine/commands/comment.py +5 -3
- opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
- opentrons/protocol_engine/commands/custom.py +5 -3
- opentrons/protocol_engine/commands/dispense.py +42 -20
- opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
- opentrons/protocol_engine/commands/drop_tip.py +68 -15
- opentrons/protocol_engine/commands/drop_tip_in_place.py +52 -11
- opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
- opentrons/protocol_engine/commands/home.py +11 -5
- opentrons/protocol_engine/commands/liquid_probe.py +146 -88
- opentrons/protocol_engine/commands/load_labware.py +19 -5
- opentrons/protocol_engine/commands/load_liquid.py +18 -7
- opentrons/protocol_engine/commands/load_module.py +43 -6
- opentrons/protocol_engine/commands/load_pipette.py +18 -17
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
- opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
- opentrons/protocol_engine/commands/move_labware.py +106 -19
- opentrons/protocol_engine/commands/move_relative.py +15 -3
- opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
- opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
- opentrons/protocol_engine/commands/move_to_well.py +37 -10
- opentrons/protocol_engine/commands/pick_up_tip.py +50 -29
- opentrons/protocol_engine/commands/pipetting_common.py +39 -15
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
- opentrons/protocol_engine/commands/reload_labware.py +13 -4
- opentrons/protocol_engine/commands/retract_axis.py +6 -3
- opentrons/protocol_engine/commands/save_position.py +2 -3
- opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
- opentrons/protocol_engine/commands/set_status_bar.py +5 -3
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
- opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
- opentrons/protocol_engine/commands/touch_tip.py +19 -7
- opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +194 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +75 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -3
- opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
- opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
- opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
- opentrons/protocol_engine/create_protocol_engine.py +41 -8
- opentrons/protocol_engine/engine_support.py +2 -1
- opentrons/protocol_engine/error_recovery_policy.py +14 -3
- opentrons/protocol_engine/errors/__init__.py +18 -0
- opentrons/protocol_engine/errors/exceptions.py +114 -2
- opentrons/protocol_engine/execution/__init__.py +2 -0
- opentrons/protocol_engine/execution/command_executor.py +22 -13
- opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
- opentrons/protocol_engine/execution/door_watcher.py +1 -1
- opentrons/protocol_engine/execution/equipment.py +2 -1
- opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
- opentrons/protocol_engine/execution/gantry_mover.py +4 -2
- opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
- opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
- opentrons/protocol_engine/execution/labware_movement.py +6 -3
- opentrons/protocol_engine/execution/movement.py +8 -3
- opentrons/protocol_engine/execution/pipetting.py +7 -4
- opentrons/protocol_engine/execution/queue_worker.py +6 -2
- opentrons/protocol_engine/execution/run_control.py +1 -1
- opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
- opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
- opentrons/protocol_engine/execution/tip_handler.py +77 -43
- opentrons/protocol_engine/notes/__init__.py +14 -2
- opentrons/protocol_engine/notes/notes.py +18 -1
- opentrons/protocol_engine/plugins.py +1 -1
- opentrons/protocol_engine/protocol_engine.py +54 -31
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +58 -5
- opentrons/protocol_engine/resources/file_provider.py +157 -0
- opentrons/protocol_engine/resources/fixture_validation.py +5 -0
- opentrons/protocol_engine/resources/labware_validation.py +10 -0
- opentrons/protocol_engine/state/__init__.py +0 -70
- opentrons/protocol_engine/state/addressable_areas.py +1 -1
- opentrons/protocol_engine/state/command_history.py +21 -2
- opentrons/protocol_engine/state/commands.py +110 -31
- opentrons/protocol_engine/state/files.py +59 -0
- opentrons/protocol_engine/state/frustum_helpers.py +440 -0
- opentrons/protocol_engine/state/geometry.py +359 -15
- opentrons/protocol_engine/state/labware.py +166 -63
- opentrons/protocol_engine/state/liquids.py +1 -1
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +19 -3
- opentrons/protocol_engine/state/modules.py +167 -85
- opentrons/protocol_engine/state/motion.py +16 -9
- opentrons/protocol_engine/state/pipettes.py +157 -317
- opentrons/protocol_engine/state/state.py +30 -1
- opentrons/protocol_engine/state/state_summary.py +3 -0
- opentrons/protocol_engine/state/tips.py +69 -114
- opentrons/protocol_engine/state/update_types.py +408 -0
- opentrons/protocol_engine/state/wells.py +236 -0
- opentrons/protocol_engine/types.py +90 -0
- opentrons/protocol_reader/file_format_validator.py +83 -15
- opentrons/protocol_runner/json_translator.py +21 -5
- opentrons/protocol_runner/legacy_command_mapper.py +27 -6
- opentrons/protocol_runner/legacy_context_plugin.py +27 -71
- opentrons/protocol_runner/protocol_runner.py +6 -3
- opentrons/protocol_runner/run_orchestrator.py +26 -6
- opentrons/protocols/advanced_control/mix.py +3 -5
- opentrons/protocols/advanced_control/transfers.py +125 -56
- opentrons/protocols/api_support/constants.py +1 -1
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/labware_like.py +4 -4
- opentrons/protocols/api_support/tip_tracker.py +2 -2
- opentrons/protocols/api_support/types.py +15 -2
- opentrons/protocols/api_support/util.py +30 -42
- opentrons/protocols/duration/errors.py +1 -1
- opentrons/protocols/duration/estimator.py +50 -29
- opentrons/protocols/execution/dev_types.py +2 -2
- opentrons/protocols/execution/execute_json_v4.py +15 -10
- opentrons/protocols/execution/execute_python.py +8 -3
- opentrons/protocols/geometry/planning.py +12 -12
- opentrons/protocols/labware.py +17 -33
- opentrons/simulate.py +3 -3
- opentrons/types.py +30 -3
- opentrons/util/logging_config.py +34 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/METADATA +5 -4
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/RECORD +227 -215
- opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
- opentrons/protocol_engine/commands/configuring_common.py +0 -26
- opentrons/protocol_runner/thread_async_queue.py +0 -174
- /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
- /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/LICENSE +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/WHEEL +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Command models to close the lid on an Absorbance Reader."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import Optional, Literal, TYPE_CHECKING
|
|
4
|
+
from typing_extensions import Type
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
9
|
+
from ...errors.error_occurrence import ErrorOccurrence
|
|
10
|
+
from ...errors import CannotPerformModuleAction
|
|
11
|
+
|
|
12
|
+
from opentrons.protocol_engine.resources import labware_validation
|
|
13
|
+
from opentrons.protocol_engine.types import AddressableAreaLocation
|
|
14
|
+
|
|
15
|
+
from opentrons.drivers.types import AbsorbanceReaderLidStatus
|
|
16
|
+
|
|
17
|
+
from ...state.update_types import StateUpdate
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from opentrons.protocol_engine.state.state import StateView
|
|
22
|
+
from opentrons.protocol_engine.execution import (
|
|
23
|
+
EquipmentHandler,
|
|
24
|
+
LabwareMovementHandler,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
OpenLidCommandType = Literal["absorbanceReader/openLid"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class OpenLidParams(BaseModel):
|
|
32
|
+
"""Input parameters to open the lid on an absorbance reading."""
|
|
33
|
+
|
|
34
|
+
moduleId: str = Field(..., description="Unique ID of the absorbance reader.")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class OpenLidResult(BaseModel):
|
|
38
|
+
"""Result data from opening the lid on an aborbance reading."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class OpenLidImpl(AbstractCommandImpl[OpenLidParams, SuccessData[OpenLidResult]]):
|
|
42
|
+
"""Execution implementation of opening the lid on an Absorbance Reader."""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
state_view: StateView,
|
|
47
|
+
equipment: EquipmentHandler,
|
|
48
|
+
labware_movement: LabwareMovementHandler,
|
|
49
|
+
**unused_dependencies: object,
|
|
50
|
+
) -> None:
|
|
51
|
+
self._state_view = state_view
|
|
52
|
+
self._equipment = equipment
|
|
53
|
+
self._labware_movement = labware_movement
|
|
54
|
+
|
|
55
|
+
async def execute(self, params: OpenLidParams) -> SuccessData[OpenLidResult]:
|
|
56
|
+
"""Move the absorbance reader lid from the module to the lid dock."""
|
|
57
|
+
mod_substate = self._state_view.modules.get_absorbance_reader_substate(
|
|
58
|
+
module_id=params.moduleId
|
|
59
|
+
)
|
|
60
|
+
# lid should currently be on the module
|
|
61
|
+
assert mod_substate.lid_id is not None
|
|
62
|
+
loaded_lid = self._state_view.labware.get(mod_substate.lid_id)
|
|
63
|
+
assert labware_validation.is_absorbance_reader_lid(loaded_lid.loadName)
|
|
64
|
+
|
|
65
|
+
hardware_lid_status = AbsorbanceReaderLidStatus.ON
|
|
66
|
+
# If the lid is closed, if the lid is open No-op out
|
|
67
|
+
if not self._state_view.config.use_virtual_modules:
|
|
68
|
+
abs_reader = self._equipment.get_module_hardware_api(mod_substate.module_id)
|
|
69
|
+
|
|
70
|
+
if abs_reader is not None:
|
|
71
|
+
result = await abs_reader.get_current_lid_status()
|
|
72
|
+
hardware_lid_status = result
|
|
73
|
+
else:
|
|
74
|
+
raise CannotPerformModuleAction(
|
|
75
|
+
"Could not reach the Hardware API for Opentrons Plate Reader Module."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# If the lid is already OFF, no-op the lid removal
|
|
79
|
+
if hardware_lid_status is AbsorbanceReaderLidStatus.OFF:
|
|
80
|
+
assert isinstance(loaded_lid.location, AddressableAreaLocation)
|
|
81
|
+
new_location = loaded_lid.location
|
|
82
|
+
new_offset_id = self._equipment.find_applicable_labware_offset_id(
|
|
83
|
+
labware_definition_uri=loaded_lid.definitionUri,
|
|
84
|
+
labware_location=loaded_lid.location,
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
# Allow propagation of ModuleNotAttachedError.
|
|
88
|
+
_ = self._equipment.get_module_hardware_api(mod_substate.module_id)
|
|
89
|
+
|
|
90
|
+
absorbance_model = self._state_view.modules.get_requested_model(
|
|
91
|
+
params.moduleId
|
|
92
|
+
)
|
|
93
|
+
assert absorbance_model is not None
|
|
94
|
+
current_location = AddressableAreaLocation(
|
|
95
|
+
addressableAreaName=self._state_view.modules.ensure_and_convert_module_fixture_location(
|
|
96
|
+
deck_slot=self._state_view.modules.get_location(
|
|
97
|
+
params.moduleId
|
|
98
|
+
).slotName,
|
|
99
|
+
deck_type=self._state_view.config.deck_type,
|
|
100
|
+
model=absorbance_model,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# we need to move the lid to the lid dock
|
|
105
|
+
new_location = self._state_view.modules.absorbance_reader_dock_location(
|
|
106
|
+
mod_substate.module_id
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
lid_gripper_offsets = self._state_view.labware.get_labware_gripper_offsets(
|
|
110
|
+
loaded_lid.id, None
|
|
111
|
+
)
|
|
112
|
+
if lid_gripper_offsets is None:
|
|
113
|
+
raise ValueError(
|
|
114
|
+
"Gripper Offset values for Absorbance Reader Lid labware must not be None."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Skips gripper moves when using virtual gripper
|
|
118
|
+
await self._labware_movement.move_labware_with_gripper(
|
|
119
|
+
labware_id=loaded_lid.id,
|
|
120
|
+
current_location=current_location,
|
|
121
|
+
new_location=new_location,
|
|
122
|
+
user_offset_data=lid_gripper_offsets,
|
|
123
|
+
post_drop_slide_offset=None,
|
|
124
|
+
)
|
|
125
|
+
new_offset_id = self._equipment.find_applicable_labware_offset_id(
|
|
126
|
+
labware_definition_uri=loaded_lid.definitionUri,
|
|
127
|
+
labware_location=new_location,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
state_update = StateUpdate()
|
|
131
|
+
|
|
132
|
+
state_update.set_labware_location(
|
|
133
|
+
labware_id=loaded_lid.id,
|
|
134
|
+
new_location=new_location,
|
|
135
|
+
new_offset_id=new_offset_id,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return SuccessData(
|
|
139
|
+
public=OpenLidResult(),
|
|
140
|
+
state_update=state_update,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class OpenLid(BaseCommand[OpenLidParams, OpenLidResult, ErrorOccurrence]):
|
|
145
|
+
"""A command to open the lid on an Absorbance Reader."""
|
|
146
|
+
|
|
147
|
+
commandType: OpenLidCommandType = "absorbanceReader/openLid"
|
|
148
|
+
params: OpenLidParams
|
|
149
|
+
result: Optional[OpenLidResult]
|
|
150
|
+
|
|
151
|
+
_ImplementationCls: Type[OpenLidImpl] = OpenLidImpl
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class OpenLidCreate(BaseCommandCreate[OpenLidParams]):
|
|
155
|
+
"""A request to execute an Absorbance Reader open lid command."""
|
|
156
|
+
|
|
157
|
+
commandType: OpenLidCommandType = "absorbanceReader/openLid"
|
|
158
|
+
params: OpenLidParams
|
|
159
|
+
|
|
160
|
+
_CommandCls: Type[OpenLid] = OpenLid
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Command models to read absorbance."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional, Dict, TYPE_CHECKING, List
|
|
5
|
+
from typing_extensions import Literal, Type
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
10
|
+
from ...errors import CannotPerformModuleAction, StorageLimitReachedError
|
|
11
|
+
from ...errors.error_occurrence import ErrorOccurrence
|
|
12
|
+
|
|
13
|
+
from ...resources.file_provider import (
|
|
14
|
+
PlateReaderData,
|
|
15
|
+
ReadData,
|
|
16
|
+
MAXIMUM_CSV_FILE_LIMIT,
|
|
17
|
+
)
|
|
18
|
+
from ...resources import FileProvider
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from opentrons.protocol_engine.state.state import StateView
|
|
22
|
+
from opentrons.protocol_engine.execution import EquipmentHandler
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
ReadAbsorbanceCommandType = Literal["absorbanceReader/read"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ReadAbsorbanceParams(BaseModel):
|
|
29
|
+
"""Input parameters for an absorbance reading."""
|
|
30
|
+
|
|
31
|
+
moduleId: str = Field(..., description="Unique ID of the Absorbance Reader.")
|
|
32
|
+
fileName: Optional[str] = Field(
|
|
33
|
+
None,
|
|
34
|
+
description="Optional file name to use when storing the results of a measurement.",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ReadAbsorbanceResult(BaseModel):
|
|
39
|
+
"""Result data from running an aborbance reading, returned as a dictionary map of wavelengths containing a map of values by well name (eg. {450: {"A1": 0.0, ...}})."""
|
|
40
|
+
|
|
41
|
+
data: Optional[Dict[int, Dict[str, float]]] = Field(
|
|
42
|
+
..., description="Absorbance data points per wavelength."
|
|
43
|
+
)
|
|
44
|
+
fileIds: Optional[List[str]] = Field(
|
|
45
|
+
...,
|
|
46
|
+
description="List of file IDs for files output as a result of a Read action.",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ReadAbsorbanceImpl(
|
|
51
|
+
AbstractCommandImpl[ReadAbsorbanceParams, SuccessData[ReadAbsorbanceResult]]
|
|
52
|
+
):
|
|
53
|
+
"""Execution implementation of an Absorbance Reader measurement."""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
state_view: StateView,
|
|
58
|
+
equipment: EquipmentHandler,
|
|
59
|
+
file_provider: FileProvider,
|
|
60
|
+
**unused_dependencies: object,
|
|
61
|
+
) -> None:
|
|
62
|
+
self._state_view = state_view
|
|
63
|
+
self._equipment = equipment
|
|
64
|
+
self._file_provider = file_provider
|
|
65
|
+
|
|
66
|
+
async def execute( # noqa: C901
|
|
67
|
+
self, params: ReadAbsorbanceParams
|
|
68
|
+
) -> SuccessData[ReadAbsorbanceResult]:
|
|
69
|
+
"""Initiate an absorbance measurement."""
|
|
70
|
+
abs_reader_substate = self._state_view.modules.get_absorbance_reader_substate(
|
|
71
|
+
module_id=params.moduleId
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Allow propagation of ModuleNotAttachedError.
|
|
75
|
+
abs_reader = self._equipment.get_module_hardware_api(
|
|
76
|
+
abs_reader_substate.module_id
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if abs_reader_substate.configured is False:
|
|
80
|
+
raise CannotPerformModuleAction(
|
|
81
|
+
"Cannot perform Read action on Absorbance Reader without calling `.initialize(...)` first."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# TODO: we need to return a file ID and increase the file count even when a moduel is not attached
|
|
85
|
+
if (
|
|
86
|
+
params.fileName is not None
|
|
87
|
+
and abs_reader_substate.configured_wavelengths is not None
|
|
88
|
+
):
|
|
89
|
+
# Validate that the amount of files we are about to generate does not put us higher than the limit
|
|
90
|
+
if (
|
|
91
|
+
self._state_view.files.get_filecount()
|
|
92
|
+
+ len(abs_reader_substate.configured_wavelengths)
|
|
93
|
+
> MAXIMUM_CSV_FILE_LIMIT
|
|
94
|
+
):
|
|
95
|
+
raise StorageLimitReachedError(
|
|
96
|
+
message=f"Attempt to write file {params.fileName} exceeds file creation limit of {MAXIMUM_CSV_FILE_LIMIT} files."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
asbsorbance_result: Dict[int, Dict[str, float]] = {}
|
|
100
|
+
transform_results = []
|
|
101
|
+
# Handle the measurement and begin building data for return
|
|
102
|
+
if abs_reader is not None:
|
|
103
|
+
start_time = datetime.now()
|
|
104
|
+
results = await abs_reader.start_measure()
|
|
105
|
+
finish_time = datetime.now()
|
|
106
|
+
if abs_reader._measurement_config is not None:
|
|
107
|
+
sample_wavelengths = abs_reader._measurement_config.sample_wavelengths
|
|
108
|
+
for wavelength, result in zip(sample_wavelengths, results):
|
|
109
|
+
converted_values = (
|
|
110
|
+
self._state_view.modules.convert_absorbance_reader_data_points(
|
|
111
|
+
data=result
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
asbsorbance_result[wavelength] = converted_values
|
|
115
|
+
transform_results.append(
|
|
116
|
+
ReadData.construct(wavelength=wavelength, data=converted_values)
|
|
117
|
+
)
|
|
118
|
+
# Handle the virtual module case for data creation (all zeroes)
|
|
119
|
+
elif self._state_view.config.use_virtual_modules:
|
|
120
|
+
start_time = finish_time = datetime.now()
|
|
121
|
+
if abs_reader_substate.configured_wavelengths is not None:
|
|
122
|
+
for wavelength in abs_reader_substate.configured_wavelengths:
|
|
123
|
+
converted_values = (
|
|
124
|
+
self._state_view.modules.convert_absorbance_reader_data_points(
|
|
125
|
+
data=[0] * 96
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
asbsorbance_result[wavelength] = converted_values
|
|
129
|
+
transform_results.append(
|
|
130
|
+
ReadData.construct(wavelength=wavelength, data=converted_values)
|
|
131
|
+
)
|
|
132
|
+
else:
|
|
133
|
+
raise CannotPerformModuleAction(
|
|
134
|
+
"Plate Reader data cannot be requested with a module that has not been initialized."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# TODO (cb, 10-17-2024): FILE PROVIDER - Some day we may want to break the file provider behavior into a seperate API function.
|
|
138
|
+
# When this happens, we probably will to have the change the command results handler we utilize to track file IDs in engine.
|
|
139
|
+
# Today, the action handler for the FileStore looks for a ReadAbsorbanceResult command action, this will need to be delinked.
|
|
140
|
+
|
|
141
|
+
# Begin interfacing with the file provider if the user provided a filename
|
|
142
|
+
file_ids = []
|
|
143
|
+
if params.fileName is not None:
|
|
144
|
+
# Create the Plate Reader Transform
|
|
145
|
+
plate_read_result = PlateReaderData.construct(
|
|
146
|
+
read_results=transform_results,
|
|
147
|
+
reference_wavelength=abs_reader_substate.reference_wavelength,
|
|
148
|
+
start_time=start_time,
|
|
149
|
+
finish_time=finish_time,
|
|
150
|
+
serial_number=abs_reader.serial_number
|
|
151
|
+
if (abs_reader is not None and abs_reader.serial_number is not None)
|
|
152
|
+
else "VIRTUAL_SERIAL",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if isinstance(plate_read_result, PlateReaderData):
|
|
156
|
+
# Write a CSV file for each of the measurements taken
|
|
157
|
+
for measurement in plate_read_result.read_results:
|
|
158
|
+
file_id = await self._file_provider.write_csv(
|
|
159
|
+
write_data=plate_read_result.build_generic_csv(
|
|
160
|
+
filename=params.fileName,
|
|
161
|
+
measurement=measurement,
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
file_ids.append(file_id)
|
|
165
|
+
|
|
166
|
+
# Return success data to api
|
|
167
|
+
return SuccessData(
|
|
168
|
+
public=ReadAbsorbanceResult(
|
|
169
|
+
data=asbsorbance_result, fileIds=file_ids
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return SuccessData(
|
|
174
|
+
public=ReadAbsorbanceResult(data=asbsorbance_result, fileIds=file_ids),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class ReadAbsorbance(
|
|
179
|
+
BaseCommand[ReadAbsorbanceParams, ReadAbsorbanceResult, ErrorOccurrence]
|
|
180
|
+
):
|
|
181
|
+
"""A command to execute an Absorbance Reader measurement."""
|
|
182
|
+
|
|
183
|
+
commandType: ReadAbsorbanceCommandType = "absorbanceReader/read"
|
|
184
|
+
params: ReadAbsorbanceParams
|
|
185
|
+
result: Optional[ReadAbsorbanceResult]
|
|
186
|
+
|
|
187
|
+
_ImplementationCls: Type[ReadAbsorbanceImpl] = ReadAbsorbanceImpl
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class ReadAbsorbanceCreate(BaseCommandCreate[ReadAbsorbanceParams]):
|
|
191
|
+
"""A request to execute an Absorbance Reader measurement."""
|
|
192
|
+
|
|
193
|
+
commandType: ReadAbsorbanceCommandType = "absorbanceReader/read"
|
|
194
|
+
params: ReadAbsorbanceParams
|
|
195
|
+
|
|
196
|
+
_CommandCls: Type[ReadAbsorbance] = ReadAbsorbance
|
|
@@ -6,11 +6,10 @@ from typing_extensions import Literal
|
|
|
6
6
|
|
|
7
7
|
from .pipetting_common import (
|
|
8
8
|
OverpressureError,
|
|
9
|
-
OverpressureErrorInternalData,
|
|
10
9
|
PipetteIdMixin,
|
|
11
10
|
AspirateVolumeMixin,
|
|
12
11
|
FlowRateMixin,
|
|
13
|
-
|
|
12
|
+
LiquidHandlingWellLocationMixin,
|
|
14
13
|
BaseLiquidHandlingResult,
|
|
15
14
|
DestinationPositionResult,
|
|
16
15
|
)
|
|
@@ -25,12 +24,13 @@ from ..errors.error_occurrence import ErrorOccurrence
|
|
|
25
24
|
|
|
26
25
|
from opentrons.hardware_control import HardwareControlAPI
|
|
27
26
|
|
|
27
|
+
from ..state.update_types import StateUpdate, CLEAR
|
|
28
28
|
from ..types import WellLocation, WellOrigin, CurrentWell, DeckPoint
|
|
29
29
|
|
|
30
30
|
if TYPE_CHECKING:
|
|
31
31
|
from ..execution import MovementHandler, PipettingHandler
|
|
32
32
|
from ..resources import ModelUtils
|
|
33
|
-
from ..state import StateView
|
|
33
|
+
from ..state.state import StateView
|
|
34
34
|
from ..notes import CommandNoteAdder
|
|
35
35
|
|
|
36
36
|
|
|
@@ -38,7 +38,7 @@ AspirateCommandType = Literal["aspirate"]
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class AspirateParams(
|
|
41
|
-
PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin,
|
|
41
|
+
PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin, LiquidHandlingWellLocationMixin
|
|
42
42
|
):
|
|
43
43
|
"""Parameters required to aspirate from a specific well."""
|
|
44
44
|
|
|
@@ -52,8 +52,8 @@ class AspirateResult(BaseLiquidHandlingResult, DestinationPositionResult):
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
_ExecuteReturn = Union[
|
|
55
|
-
SuccessData[AspirateResult
|
|
56
|
-
DefinedErrorData[OverpressureError
|
|
55
|
+
SuccessData[AspirateResult],
|
|
56
|
+
DefinedErrorData[OverpressureError],
|
|
57
57
|
]
|
|
58
58
|
|
|
59
59
|
|
|
@@ -92,6 +92,7 @@ class AspirateImplementation(AbstractCommandImpl[AspirateParams, _ExecuteReturn]
|
|
|
92
92
|
)
|
|
93
93
|
|
|
94
94
|
current_well = None
|
|
95
|
+
state_update = StateUpdate()
|
|
95
96
|
|
|
96
97
|
if not ready_to_aspirate:
|
|
97
98
|
await self._movement.move_to_well(
|
|
@@ -117,6 +118,14 @@ class AspirateImplementation(AbstractCommandImpl[AspirateParams, _ExecuteReturn]
|
|
|
117
118
|
well_name=well_name,
|
|
118
119
|
well_location=params.wellLocation,
|
|
119
120
|
current_well=current_well,
|
|
121
|
+
operation_volume=-params.volume,
|
|
122
|
+
)
|
|
123
|
+
deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z)
|
|
124
|
+
state_update.set_pipette_location(
|
|
125
|
+
pipette_id=pipette_id,
|
|
126
|
+
new_labware_id=labware_id,
|
|
127
|
+
new_well_name=well_name,
|
|
128
|
+
new_deck_point=deck_point,
|
|
120
129
|
)
|
|
121
130
|
|
|
122
131
|
try:
|
|
@@ -127,6 +136,11 @@ class AspirateImplementation(AbstractCommandImpl[AspirateParams, _ExecuteReturn]
|
|
|
127
136
|
command_note_adder=self._command_note_adder,
|
|
128
137
|
)
|
|
129
138
|
except PipetteOverpressureError as e:
|
|
139
|
+
state_update.set_liquid_operated(
|
|
140
|
+
labware_id=labware_id,
|
|
141
|
+
well_name=well_name,
|
|
142
|
+
volume_added=CLEAR,
|
|
143
|
+
)
|
|
130
144
|
return DefinedErrorData(
|
|
131
145
|
public=OverpressureError(
|
|
132
146
|
id=self._model_utils.generate_id(),
|
|
@@ -140,25 +154,24 @@ class AspirateImplementation(AbstractCommandImpl[AspirateParams, _ExecuteReturn]
|
|
|
140
154
|
],
|
|
141
155
|
errorInfo={"retryLocation": (position.x, position.y, position.z)},
|
|
142
156
|
),
|
|
143
|
-
|
|
144
|
-
position=DeckPoint.construct(
|
|
145
|
-
x=position.x, y=position.y, z=position.z
|
|
146
|
-
)
|
|
147
|
-
),
|
|
157
|
+
state_update=state_update,
|
|
148
158
|
)
|
|
149
159
|
else:
|
|
160
|
+
state_update.set_liquid_operated(
|
|
161
|
+
labware_id=labware_id,
|
|
162
|
+
well_name=well_name,
|
|
163
|
+
volume_added=-volume_aspirated,
|
|
164
|
+
)
|
|
150
165
|
return SuccessData(
|
|
151
166
|
public=AspirateResult(
|
|
152
167
|
volume=volume_aspirated,
|
|
153
|
-
position=
|
|
154
|
-
x=position.x, y=position.y, z=position.z
|
|
155
|
-
),
|
|
168
|
+
position=deck_point,
|
|
156
169
|
),
|
|
157
|
-
|
|
170
|
+
state_update=state_update,
|
|
158
171
|
)
|
|
159
172
|
|
|
160
173
|
|
|
161
|
-
class Aspirate(BaseCommand[AspirateParams, AspirateResult,
|
|
174
|
+
class Aspirate(BaseCommand[AspirateParams, AspirateResult, OverpressureError]):
|
|
162
175
|
"""Aspirate command model."""
|
|
163
176
|
|
|
164
177
|
commandType: AspirateCommandType = "aspirate"
|
|
@@ -14,7 +14,6 @@ from .pipetting_common import (
|
|
|
14
14
|
FlowRateMixin,
|
|
15
15
|
BaseLiquidHandlingResult,
|
|
16
16
|
OverpressureError,
|
|
17
|
-
OverpressureErrorInternalData,
|
|
18
17
|
)
|
|
19
18
|
from .command import (
|
|
20
19
|
AbstractCommandImpl,
|
|
@@ -25,12 +24,13 @@ from .command import (
|
|
|
25
24
|
)
|
|
26
25
|
from ..errors.error_occurrence import ErrorOccurrence
|
|
27
26
|
from ..errors.exceptions import PipetteNotReadyToAspirateError
|
|
28
|
-
from ..
|
|
27
|
+
from ..state.update_types import StateUpdate, CLEAR
|
|
28
|
+
from ..types import CurrentWell
|
|
29
29
|
|
|
30
30
|
if TYPE_CHECKING:
|
|
31
31
|
from ..execution import PipettingHandler, GantryMover
|
|
32
32
|
from ..resources import ModelUtils
|
|
33
|
-
from ..state import StateView
|
|
33
|
+
from ..state.state import StateView
|
|
34
34
|
from ..notes import CommandNoteAdder
|
|
35
35
|
|
|
36
36
|
AspirateInPlaceCommandType = Literal["aspirateInPlace"]
|
|
@@ -49,8 +49,8 @@ class AspirateInPlaceResult(BaseLiquidHandlingResult):
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
_ExecuteReturn = Union[
|
|
52
|
-
SuccessData[AspirateInPlaceResult
|
|
53
|
-
DefinedErrorData[OverpressureError
|
|
52
|
+
SuccessData[AspirateInPlaceResult],
|
|
53
|
+
DefinedErrorData[OverpressureError],
|
|
54
54
|
]
|
|
55
55
|
|
|
56
56
|
|
|
@@ -93,7 +93,12 @@ class AspirateInPlaceImplementation(
|
|
|
93
93
|
" The first aspirate following a blow-out must be from a specific well"
|
|
94
94
|
" so the plunger can be reset in a known safe position."
|
|
95
95
|
)
|
|
96
|
+
|
|
97
|
+
state_update = StateUpdate()
|
|
98
|
+
current_location = self._state_view.pipettes.get_current_location()
|
|
99
|
+
|
|
96
100
|
try:
|
|
101
|
+
current_position = await self._gantry_mover.get_position(params.pipetteId)
|
|
97
102
|
volume = await self._pipetting.aspirate_in_place(
|
|
98
103
|
pipette_id=params.pipetteId,
|
|
99
104
|
volume=params.volume,
|
|
@@ -101,7 +106,15 @@ class AspirateInPlaceImplementation(
|
|
|
101
106
|
command_note_adder=self._command_note_adder,
|
|
102
107
|
)
|
|
103
108
|
except PipetteOverpressureError as e:
|
|
104
|
-
|
|
109
|
+
if (
|
|
110
|
+
isinstance(current_location, CurrentWell)
|
|
111
|
+
and current_location.pipette_id == params.pipetteId
|
|
112
|
+
):
|
|
113
|
+
state_update.set_liquid_operated(
|
|
114
|
+
labware_id=current_location.labware_id,
|
|
115
|
+
well_name=current_location.well_name,
|
|
116
|
+
volume_added=CLEAR,
|
|
117
|
+
)
|
|
105
118
|
return DefinedErrorData(
|
|
106
119
|
public=OverpressureError(
|
|
107
120
|
id=self._model_utils.generate_id(),
|
|
@@ -123,22 +136,26 @@ class AspirateInPlaceImplementation(
|
|
|
123
136
|
}
|
|
124
137
|
),
|
|
125
138
|
),
|
|
126
|
-
|
|
127
|
-
position=DeckPoint(
|
|
128
|
-
x=current_position.x,
|
|
129
|
-
y=current_position.y,
|
|
130
|
-
z=current_position.z,
|
|
131
|
-
),
|
|
132
|
-
),
|
|
139
|
+
state_update=state_update,
|
|
133
140
|
)
|
|
134
141
|
else:
|
|
142
|
+
if (
|
|
143
|
+
isinstance(current_location, CurrentWell)
|
|
144
|
+
and current_location.pipette_id == params.pipetteId
|
|
145
|
+
):
|
|
146
|
+
state_update.set_liquid_operated(
|
|
147
|
+
labware_id=current_location.labware_id,
|
|
148
|
+
well_name=current_location.well_name,
|
|
149
|
+
volume_added=-volume,
|
|
150
|
+
)
|
|
135
151
|
return SuccessData(
|
|
136
|
-
public=AspirateInPlaceResult(volume=volume),
|
|
152
|
+
public=AspirateInPlaceResult(volume=volume),
|
|
153
|
+
state_update=state_update,
|
|
137
154
|
)
|
|
138
155
|
|
|
139
156
|
|
|
140
157
|
class AspirateInPlace(
|
|
141
|
-
BaseCommand[AspirateInPlaceParams, AspirateInPlaceResult,
|
|
158
|
+
BaseCommand[AspirateInPlaceParams, AspirateInPlaceResult, OverpressureError]
|
|
142
159
|
):
|
|
143
160
|
"""AspirateInPlace command model."""
|
|
144
161
|
|