opentrons 8.3.2a0__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.
- 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.2a0.dist-info → opentrons-8.4.0.dist-info}/METADATA +4 -4
- {opentrons-8.3.2a0.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.2a0.dist-info → opentrons-8.4.0.dist-info}/LICENSE +0 -0
- {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/WHEEL +0 -0
- {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/top_level.txt +0 -0
|
@@ -13,10 +13,16 @@ from typing import (
|
|
|
13
13
|
Optional,
|
|
14
14
|
cast,
|
|
15
15
|
TYPE_CHECKING,
|
|
16
|
+
TypeGuard,
|
|
16
17
|
)
|
|
17
18
|
from typing_extensions import TypedDict
|
|
18
19
|
from pathlib import Path
|
|
19
20
|
|
|
21
|
+
from opentrons.drivers.flex_stacker.types import (
|
|
22
|
+
LimitSwitchStatus,
|
|
23
|
+
PlatformStatus,
|
|
24
|
+
StackerAxis,
|
|
25
|
+
)
|
|
20
26
|
from opentrons.drivers.rpi_drivers.types import USBPort
|
|
21
27
|
|
|
22
28
|
if TYPE_CHECKING:
|
|
@@ -27,6 +33,7 @@ if TYPE_CHECKING:
|
|
|
27
33
|
HeaterShakerModuleType,
|
|
28
34
|
MagneticBlockType,
|
|
29
35
|
AbsorbanceReaderType,
|
|
36
|
+
FlexStackerModuleType,
|
|
30
37
|
)
|
|
31
38
|
|
|
32
39
|
|
|
@@ -50,9 +57,113 @@ UploadFunction = Callable[[str, str, Dict[str, Any]], Awaitable[Tuple[bool, str]
|
|
|
50
57
|
ModuleDisconnectedCallback = Optional[Callable[[str, str | None], None]]
|
|
51
58
|
|
|
52
59
|
|
|
60
|
+
class MagneticModuleData(TypedDict):
|
|
61
|
+
engaged: bool
|
|
62
|
+
height: float
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TemperatureModuleData(TypedDict):
|
|
66
|
+
currentTemp: float
|
|
67
|
+
targetTemp: float | None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class HeaterShakerData(TypedDict):
|
|
71
|
+
temperatureStatus: str
|
|
72
|
+
speedStatus: str
|
|
73
|
+
labwareLatchStatus: str
|
|
74
|
+
currentTemp: float
|
|
75
|
+
targetTemp: float | None
|
|
76
|
+
currentSpeed: int
|
|
77
|
+
targetSpeed: int | None
|
|
78
|
+
errorDetails: str | None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ThermocyclerData(TypedDict):
|
|
82
|
+
lid: str
|
|
83
|
+
lidTarget: float | None
|
|
84
|
+
lidTemp: float
|
|
85
|
+
lidTempStatus: str
|
|
86
|
+
currentTemp: float | None
|
|
87
|
+
targetTemp: float | None
|
|
88
|
+
holdTime: float | None
|
|
89
|
+
rampRate: float | None
|
|
90
|
+
currentCycleIndex: int | None
|
|
91
|
+
totalCycleCount: int | None
|
|
92
|
+
currentStepIndex: int | None
|
|
93
|
+
totalStepCount: int | None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class AbsorbanceReaderData(TypedDict):
|
|
97
|
+
uptime: int
|
|
98
|
+
deviceStatus: str
|
|
99
|
+
lidStatus: str
|
|
100
|
+
platePresence: str
|
|
101
|
+
measureMode: str
|
|
102
|
+
sampleWavelengths: List[int]
|
|
103
|
+
referenceWavelength: int
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class FlexStackerData(TypedDict):
|
|
107
|
+
latchState: str
|
|
108
|
+
platformState: str
|
|
109
|
+
hopperDoorState: str
|
|
110
|
+
axisStateX: str
|
|
111
|
+
axisStateZ: str
|
|
112
|
+
errorDetails: str | None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
ModuleData = Union[
|
|
116
|
+
Dict[Any, Any], # This allows an empty dict as module data
|
|
117
|
+
MagneticModuleData,
|
|
118
|
+
TemperatureModuleData,
|
|
119
|
+
HeaterShakerData,
|
|
120
|
+
ThermocyclerData,
|
|
121
|
+
AbsorbanceReaderData,
|
|
122
|
+
FlexStackerData,
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ModuleDataValidator:
|
|
127
|
+
@classmethod
|
|
128
|
+
def is_magnetic_module_data(
|
|
129
|
+
cls, data: ModuleData | None
|
|
130
|
+
) -> TypeGuard[MagneticModuleData]:
|
|
131
|
+
return data is not None and "engaged" in data.keys()
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def is_temperature_module_data(
|
|
135
|
+
cls, data: ModuleData | None
|
|
136
|
+
) -> TypeGuard[TemperatureModuleData]:
|
|
137
|
+
return data is not None and "targetTemp" in data.keys()
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def is_heater_shaker_data(
|
|
141
|
+
cls, data: ModuleData | None
|
|
142
|
+
) -> TypeGuard[HeaterShakerData]:
|
|
143
|
+
return data is not None and "labwareLatchStatus" in data.keys()
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def is_thermocycler_data(
|
|
147
|
+
cls, data: ModuleData | None
|
|
148
|
+
) -> TypeGuard[ThermocyclerData]:
|
|
149
|
+
return data is not None and "lid" in data.keys()
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def is_absorbance_reader_data(
|
|
153
|
+
cls, data: ModuleData | None
|
|
154
|
+
) -> TypeGuard[AbsorbanceReaderData]:
|
|
155
|
+
return data is not None and "uptime" in data.keys()
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def is_flex_stacker_data(
|
|
159
|
+
cls, data: ModuleData | None
|
|
160
|
+
) -> TypeGuard[FlexStackerData]:
|
|
161
|
+
return data is not None and "platformState" in data.keys()
|
|
162
|
+
|
|
163
|
+
|
|
53
164
|
class LiveData(TypedDict):
|
|
54
165
|
status: str
|
|
55
|
-
data:
|
|
166
|
+
data: ModuleData | None
|
|
56
167
|
|
|
57
168
|
|
|
58
169
|
class ModuleType(str, Enum):
|
|
@@ -62,6 +173,7 @@ class ModuleType(str, Enum):
|
|
|
62
173
|
HEATER_SHAKER: HeaterShakerModuleType = "heaterShakerModuleType"
|
|
63
174
|
MAGNETIC_BLOCK: MagneticBlockType = "magneticBlockType"
|
|
64
175
|
ABSORBANCE_READER: AbsorbanceReaderType = "absorbanceReaderType"
|
|
176
|
+
FLEX_STACKER: FlexStackerModuleType = "flexStackerModuleType"
|
|
65
177
|
|
|
66
178
|
@classmethod
|
|
67
179
|
def from_model(cls, model: ModuleModel) -> ModuleType:
|
|
@@ -77,6 +189,8 @@ class ModuleType(str, Enum):
|
|
|
77
189
|
return cls.MAGNETIC_BLOCK
|
|
78
190
|
if isinstance(model, AbsorbanceReaderModel):
|
|
79
191
|
return cls.ABSORBANCE_READER
|
|
192
|
+
if isinstance(model, FlexStackerModuleModel):
|
|
193
|
+
return cls.FLEX_STACKER
|
|
80
194
|
|
|
81
195
|
@classmethod
|
|
82
196
|
def to_module_fixture_id(cls, module_type: ModuleType) -> str:
|
|
@@ -91,6 +205,8 @@ class ModuleType(str, Enum):
|
|
|
91
205
|
return "magneticBlockV1"
|
|
92
206
|
if module_type == ModuleType.ABSORBANCE_READER:
|
|
93
207
|
return "absorbanceReaderV1"
|
|
208
|
+
if module_type == ModuleType.FLEX_STACKER:
|
|
209
|
+
return "flexStackerModuleV1"
|
|
94
210
|
else:
|
|
95
211
|
raise ValueError(
|
|
96
212
|
f"Module Type {module_type} does not have a related fixture ID."
|
|
@@ -124,6 +240,10 @@ class AbsorbanceReaderModel(str, Enum):
|
|
|
124
240
|
ABSORBANCE_READER_V1: str = "absorbanceReaderV1"
|
|
125
241
|
|
|
126
242
|
|
|
243
|
+
class FlexStackerModuleModel(str, Enum):
|
|
244
|
+
FLEX_STACKER_V1: str = "flexStackerModuleV1"
|
|
245
|
+
|
|
246
|
+
|
|
127
247
|
def module_model_from_string(model_string: str) -> ModuleModel:
|
|
128
248
|
for model_enum in {
|
|
129
249
|
MagneticModuleModel,
|
|
@@ -132,6 +252,7 @@ def module_model_from_string(model_string: str) -> ModuleModel:
|
|
|
132
252
|
HeaterShakerModuleModel,
|
|
133
253
|
MagneticBlockModel,
|
|
134
254
|
AbsorbanceReaderModel,
|
|
255
|
+
FlexStackerModuleModel,
|
|
135
256
|
}:
|
|
136
257
|
try:
|
|
137
258
|
return cast(ModuleModel, model_enum(model_string))
|
|
@@ -184,6 +305,7 @@ ModuleModel = Union[
|
|
|
184
305
|
HeaterShakerModuleModel,
|
|
185
306
|
MagneticBlockModel,
|
|
186
307
|
AbsorbanceReaderModel,
|
|
308
|
+
FlexStackerModuleModel,
|
|
187
309
|
]
|
|
188
310
|
|
|
189
311
|
|
|
@@ -225,3 +347,71 @@ class LidStatus(str, Enum):
|
|
|
225
347
|
OFF = "off"
|
|
226
348
|
UNKNOWN = "unknown"
|
|
227
349
|
ERROR = "error"
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class FlexStackerStatus(str, Enum):
|
|
353
|
+
IDLE = "idle"
|
|
354
|
+
DISPENSING = "dispensing"
|
|
355
|
+
STORING = "storing"
|
|
356
|
+
ERROR = "error"
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class PlatformState(str, Enum):
|
|
360
|
+
UNKNOWN = "unknown"
|
|
361
|
+
EXTENDED = "extended"
|
|
362
|
+
RETRACTED = "retracted"
|
|
363
|
+
|
|
364
|
+
@classmethod
|
|
365
|
+
def from_status(cls, status: PlatformStatus) -> "PlatformState":
|
|
366
|
+
"""Get the state from the platform status."""
|
|
367
|
+
if status.E and not status.R:
|
|
368
|
+
return cls.EXTENDED
|
|
369
|
+
if status.R and not status.E:
|
|
370
|
+
return cls.RETRACTED
|
|
371
|
+
return cls.UNKNOWN
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
class StackerAxisState(str, Enum):
|
|
375
|
+
UNKNOWN = "unknown"
|
|
376
|
+
EXTENDED = "extended"
|
|
377
|
+
RETRACTED = "retracted"
|
|
378
|
+
|
|
379
|
+
@classmethod
|
|
380
|
+
def from_status(
|
|
381
|
+
cls, status: LimitSwitchStatus, axis: StackerAxis
|
|
382
|
+
) -> "StackerAxisState":
|
|
383
|
+
"""Get the axis state from the limit switch status."""
|
|
384
|
+
match axis:
|
|
385
|
+
case StackerAxis.X:
|
|
386
|
+
if status.XE and not status.XR:
|
|
387
|
+
return cls.EXTENDED
|
|
388
|
+
if status.XR and not status.XE:
|
|
389
|
+
return cls.RETRACTED
|
|
390
|
+
case StackerAxis.Z:
|
|
391
|
+
if status.ZE and not status.ZR:
|
|
392
|
+
return cls.EXTENDED
|
|
393
|
+
if status.ZR and not status.ZE:
|
|
394
|
+
return cls.RETRACTED
|
|
395
|
+
case StackerAxis.L:
|
|
396
|
+
return cls.EXTENDED if status.LR else cls.RETRACTED
|
|
397
|
+
return cls.UNKNOWN
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class LatchState(str, Enum):
|
|
401
|
+
CLOSED = "closed"
|
|
402
|
+
OPENED = "opened"
|
|
403
|
+
|
|
404
|
+
@classmethod
|
|
405
|
+
def from_state(cls, state: StackerAxisState) -> "LatchState":
|
|
406
|
+
"""Get the latch state from the axis state."""
|
|
407
|
+
return cls.CLOSED if state == StackerAxisState.EXTENDED else cls.OPENED
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class HopperDoorState(str, Enum):
|
|
411
|
+
CLOSED = "closed"
|
|
412
|
+
OPENED = "opened"
|
|
413
|
+
|
|
414
|
+
@classmethod
|
|
415
|
+
def from_state(cls, state: bool) -> "HopperDoorState":
|
|
416
|
+
"""Get the hopper door state from the door state boolean."""
|
|
417
|
+
return cls.CLOSED if state else cls.OPENED
|
|
@@ -13,6 +13,7 @@ from .magdeck import MagDeck
|
|
|
13
13
|
from .thermocycler import Thermocycler
|
|
14
14
|
from .heater_shaker import HeaterShaker
|
|
15
15
|
from .absorbance_reader import AbsorbanceReader
|
|
16
|
+
from .flex_stacker import FlexStacker
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
log = logging.getLogger(__name__)
|
|
@@ -26,6 +27,7 @@ MODULE_TYPE_BY_NAME = {
|
|
|
26
27
|
Thermocycler.name(): Thermocycler.MODULE_TYPE,
|
|
27
28
|
HeaterShaker.name(): HeaterShaker.MODULE_TYPE,
|
|
28
29
|
AbsorbanceReader.name(): AbsorbanceReader.MODULE_TYPE,
|
|
30
|
+
FlexStacker.name(): FlexStacker.MODULE_TYPE,
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
_MODULE_CLS_BY_TYPE: Dict[ModuleType, Type[AbstractModule]] = {
|
|
@@ -34,6 +36,7 @@ _MODULE_CLS_BY_TYPE: Dict[ModuleType, Type[AbstractModule]] = {
|
|
|
34
36
|
Thermocycler.MODULE_TYPE: Thermocycler,
|
|
35
37
|
HeaterShaker.MODULE_TYPE: HeaterShaker,
|
|
36
38
|
AbsorbanceReader.MODULE_TYPE: AbsorbanceReader,
|
|
39
|
+
FlexStacker.MODULE_TYPE: FlexStacker,
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
|
|
@@ -192,6 +192,26 @@ def target_position_from_plunger(
|
|
|
192
192
|
return all_axes_pos
|
|
193
193
|
|
|
194
194
|
|
|
195
|
+
def target_positions_from_plunger_tracking(
|
|
196
|
+
mount: Union[Mount, OT3Mount],
|
|
197
|
+
plunger_delta: float,
|
|
198
|
+
z_delta: float,
|
|
199
|
+
current_position: Dict[Axis, float],
|
|
200
|
+
) -> "OrderedDict[Axis, float]":
|
|
201
|
+
"""Create a target position for machine axes including plungers for dynamic liquid tracking.
|
|
202
|
+
|
|
203
|
+
The x/y axes remain constant but the plunger and Z move to create a tracking action.
|
|
204
|
+
|
|
205
|
+
plunger_delta: the distance the plunger should move- should be determined based on desired
|
|
206
|
+
volume to aspirate/dispense.
|
|
207
|
+
z_delta: the distance to move the z axis- should be determined based on volume and well geometry.
|
|
208
|
+
"""
|
|
209
|
+
all_axes_pos = target_position_from_plunger(mount, plunger_delta, current_position)
|
|
210
|
+
z_ax = Axis.by_mount(mount)
|
|
211
|
+
all_axes_pos[z_ax] = current_position[z_ax] + z_delta
|
|
212
|
+
return all_axes_pos
|
|
213
|
+
|
|
214
|
+
|
|
195
215
|
def deck_point_from_machine_point(
|
|
196
216
|
machine_point: Point, attitude: AttitudeMatrix, offset: Point
|
|
197
217
|
) -> Point:
|
|
@@ -99,6 +99,7 @@ from .types import (
|
|
|
99
99
|
HardwareFeatureFlags,
|
|
100
100
|
FailedTipStateCheck,
|
|
101
101
|
PipetteSensorResponseQueue,
|
|
102
|
+
TipScrapeType,
|
|
102
103
|
)
|
|
103
104
|
from .errors import (
|
|
104
105
|
UpdateOngoingError,
|
|
@@ -126,6 +127,7 @@ from .motion_utilities import (
|
|
|
126
127
|
target_position_from_absolute,
|
|
127
128
|
target_position_from_relative,
|
|
128
129
|
target_position_from_plunger,
|
|
130
|
+
target_positions_from_plunger_tracking,
|
|
129
131
|
offset_for_mount,
|
|
130
132
|
deck_from_machine,
|
|
131
133
|
machine_from_deck,
|
|
@@ -450,9 +452,11 @@ class OT3API(
|
|
|
450
452
|
checked_config = config
|
|
451
453
|
|
|
452
454
|
backend = await OT3Simulator.build(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
455
|
+
(
|
|
456
|
+
{OT3Mount.from_mount(k): v for k, v in attached_instruments.items()}
|
|
457
|
+
if attached_instruments
|
|
458
|
+
else {}
|
|
459
|
+
),
|
|
456
460
|
checked_modules,
|
|
457
461
|
checked_config,
|
|
458
462
|
checked_loop,
|
|
@@ -2051,12 +2055,16 @@ class OT3API(
|
|
|
2051
2055
|
mount: Union[top_types.Mount, OT3Mount],
|
|
2052
2056
|
volume: Optional[float] = None,
|
|
2053
2057
|
rate: float = 1.0,
|
|
2058
|
+
correction_volume: float = 0.0,
|
|
2054
2059
|
) -> None:
|
|
2055
2060
|
"""
|
|
2056
2061
|
Aspirate a volume of liquid (in microliters/uL) using this pipette."""
|
|
2057
2062
|
realmount = OT3Mount.from_mount(mount)
|
|
2058
2063
|
aspirate_spec = self._pipette_handler.plan_check_aspirate(
|
|
2059
|
-
realmount,
|
|
2064
|
+
mount=realmount,
|
|
2065
|
+
volume=volume,
|
|
2066
|
+
rate=rate,
|
|
2067
|
+
correction_volume=correction_volume,
|
|
2060
2068
|
)
|
|
2061
2069
|
if not aspirate_spec:
|
|
2062
2070
|
return
|
|
@@ -2093,12 +2101,19 @@ class OT3API(
|
|
|
2093
2101
|
volume: Optional[float] = None,
|
|
2094
2102
|
rate: float = 1.0,
|
|
2095
2103
|
push_out: Optional[float] = None,
|
|
2104
|
+
correction_volume: float = 0.0,
|
|
2105
|
+
is_full_dispense: bool = False,
|
|
2096
2106
|
) -> None:
|
|
2097
2107
|
"""
|
|
2098
2108
|
Dispense a volume of liquid in microliters(uL) using this pipette."""
|
|
2099
2109
|
realmount = OT3Mount.from_mount(mount)
|
|
2100
2110
|
dispense_spec = self._pipette_handler.plan_check_dispense(
|
|
2101
|
-
realmount,
|
|
2111
|
+
mount=realmount,
|
|
2112
|
+
volume=volume,
|
|
2113
|
+
rate=rate,
|
|
2114
|
+
push_out=push_out,
|
|
2115
|
+
is_full_dispense=is_full_dispense,
|
|
2116
|
+
correction_volume=correction_volume,
|
|
2102
2117
|
)
|
|
2103
2118
|
if not dispense_spec:
|
|
2104
2119
|
return
|
|
@@ -2329,6 +2344,7 @@ class OT3API(
|
|
|
2329
2344
|
mount: Union[top_types.Mount, OT3Mount],
|
|
2330
2345
|
home_after: bool = False,
|
|
2331
2346
|
ignore_plunger: bool = False,
|
|
2347
|
+
scrape_type: TipScrapeType = TipScrapeType.NONE,
|
|
2332
2348
|
) -> None:
|
|
2333
2349
|
realmount = OT3Mount.from_mount(mount)
|
|
2334
2350
|
if ignore_plunger is False:
|
|
@@ -2340,18 +2356,27 @@ class OT3API(
|
|
|
2340
2356
|
spec = self._pipette_handler.plan_ht_drop_tip()
|
|
2341
2357
|
await self._tip_motor_action(realmount, spec.tip_action_moves)
|
|
2342
2358
|
else:
|
|
2343
|
-
spec = self._pipette_handler.plan_lt_drop_tip(realmount)
|
|
2359
|
+
spec = self._pipette_handler.plan_lt_drop_tip(realmount, scrape_type)
|
|
2344
2360
|
for move in spec.tip_action_moves:
|
|
2345
2361
|
async with self._backend.motor_current(move.currents):
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2362
|
+
if not move.scrape_axis:
|
|
2363
|
+
target_pos = target_position_from_plunger(
|
|
2364
|
+
realmount, move.distance, self._current_position
|
|
2365
|
+
)
|
|
2366
|
+
await self._move(
|
|
2367
|
+
target_pos,
|
|
2368
|
+
speed=move.speed,
|
|
2369
|
+
home_flagged_axes=False,
|
|
2370
|
+
)
|
|
2371
|
+
else:
|
|
2372
|
+
target_pos = OrderedDict(self._current_position)
|
|
2373
|
+
target_pos[move.scrape_axis] += move.distance
|
|
2374
|
+
self._log.info(f"Moving to target Pos: {target_pos}")
|
|
2375
|
+
await self._move(
|
|
2376
|
+
target_pos,
|
|
2377
|
+
speed=move.speed,
|
|
2378
|
+
home_flagged_axes=False,
|
|
2379
|
+
)
|
|
2355
2380
|
for shake in spec.shake_off_moves:
|
|
2356
2381
|
await self.move_rel(mount, shake[0], speed=shake[1])
|
|
2357
2382
|
|
|
@@ -2974,6 +2999,103 @@ class OT3API(
|
|
|
2974
2999
|
|
|
2975
3000
|
AMKey = TypeVar("AMKey")
|
|
2976
3001
|
|
|
3002
|
+
async def aspirate_while_tracking(
|
|
3003
|
+
self,
|
|
3004
|
+
mount: Union[top_types.Mount, OT3Mount],
|
|
3005
|
+
z_distance: float,
|
|
3006
|
+
volume: float,
|
|
3007
|
+
flow_rate: float = 1.0,
|
|
3008
|
+
) -> None:
|
|
3009
|
+
"""
|
|
3010
|
+
Aspirate a volume of liquid (in microliters/uL) while moving the z axis synchronously.
|
|
3011
|
+
|
|
3012
|
+
:param mount: A robot mount that the instrument is on.
|
|
3013
|
+
:param z_distance: The distance the z axis will move during apsiration.
|
|
3014
|
+
:param volume: The volume of liquid to be aspirated.
|
|
3015
|
+
:param flow_rate: The flow rate to aspirate with.
|
|
3016
|
+
"""
|
|
3017
|
+
realmount = OT3Mount.from_mount(mount)
|
|
3018
|
+
aspirate_spec = self._pipette_handler.plan_check_aspirate(
|
|
3019
|
+
realmount, volume, flow_rate
|
|
3020
|
+
)
|
|
3021
|
+
if not aspirate_spec:
|
|
3022
|
+
return
|
|
3023
|
+
target_pos = target_positions_from_plunger_tracking(
|
|
3024
|
+
realmount,
|
|
3025
|
+
aspirate_spec.plunger_distance,
|
|
3026
|
+
z_distance,
|
|
3027
|
+
self._current_position,
|
|
3028
|
+
)
|
|
3029
|
+
try:
|
|
3030
|
+
await self._backend.set_active_current(
|
|
3031
|
+
{aspirate_spec.axis: aspirate_spec.current}
|
|
3032
|
+
)
|
|
3033
|
+
async with self.restore_system_constrants():
|
|
3034
|
+
await self.set_system_constraints_for_plunger_acceleration(
|
|
3035
|
+
realmount, aspirate_spec.acceleration
|
|
3036
|
+
)
|
|
3037
|
+
await self._move(
|
|
3038
|
+
target_pos,
|
|
3039
|
+
speed=aspirate_spec.speed,
|
|
3040
|
+
home_flagged_axes=False,
|
|
3041
|
+
)
|
|
3042
|
+
except Exception:
|
|
3043
|
+
self._log.exception("Aspirate failed")
|
|
3044
|
+
aspirate_spec.instr.set_current_volume(0)
|
|
3045
|
+
raise
|
|
3046
|
+
else:
|
|
3047
|
+
aspirate_spec.instr.add_current_volume(aspirate_spec.volume)
|
|
3048
|
+
|
|
3049
|
+
async def dispense_while_tracking(
|
|
3050
|
+
self,
|
|
3051
|
+
mount: Union[top_types.Mount, OT3Mount],
|
|
3052
|
+
z_distance: float,
|
|
3053
|
+
volume: float,
|
|
3054
|
+
push_out: Optional[float],
|
|
3055
|
+
flow_rate: float = 1.0,
|
|
3056
|
+
is_full_dispense: bool = False,
|
|
3057
|
+
) -> None:
|
|
3058
|
+
"""
|
|
3059
|
+
Dispense a volume of liquid (in microliters/uL) while moving the z axis synchronously.
|
|
3060
|
+
|
|
3061
|
+
:param mount: A robot mount that the instrument is on.
|
|
3062
|
+
:param z_distance: The distance the z axis will move during dispensing.
|
|
3063
|
+
:param volume: The volume of liquid to be dispensed.
|
|
3064
|
+
:param flow_rate: The flow rate to dispense with.
|
|
3065
|
+
"""
|
|
3066
|
+
realmount = OT3Mount.from_mount(mount)
|
|
3067
|
+
dispense_spec = self._pipette_handler.plan_check_dispense(
|
|
3068
|
+
realmount, volume, flow_rate, push_out, is_full_dispense
|
|
3069
|
+
)
|
|
3070
|
+
if not dispense_spec:
|
|
3071
|
+
return
|
|
3072
|
+
target_pos = target_positions_from_plunger_tracking(
|
|
3073
|
+
realmount,
|
|
3074
|
+
dispense_spec.plunger_distance,
|
|
3075
|
+
z_distance,
|
|
3076
|
+
self._current_position,
|
|
3077
|
+
)
|
|
3078
|
+
|
|
3079
|
+
try:
|
|
3080
|
+
await self._backend.set_active_current(
|
|
3081
|
+
{dispense_spec.axis: dispense_spec.current}
|
|
3082
|
+
)
|
|
3083
|
+
async with self.restore_system_constrants():
|
|
3084
|
+
await self.set_system_constraints_for_plunger_acceleration(
|
|
3085
|
+
realmount, dispense_spec.acceleration
|
|
3086
|
+
)
|
|
3087
|
+
await self._move(
|
|
3088
|
+
target_pos,
|
|
3089
|
+
speed=dispense_spec.speed,
|
|
3090
|
+
home_flagged_axes=False,
|
|
3091
|
+
)
|
|
3092
|
+
except Exception:
|
|
3093
|
+
self._log.exception("dispense failed")
|
|
3094
|
+
dispense_spec.instr.set_current_volume(0)
|
|
3095
|
+
raise
|
|
3096
|
+
else:
|
|
3097
|
+
dispense_spec.instr.remove_current_volume(dispense_spec.volume)
|
|
3098
|
+
|
|
2977
3099
|
@property
|
|
2978
3100
|
def attached_subsystems(self) -> Dict[SubSystem, SubSystemState]:
|
|
2979
3101
|
"""Get a view of the state of the currently-attached subsystems."""
|
|
@@ -3010,3 +3132,11 @@ class OT3API(
|
|
|
3010
3132
|
|
|
3011
3133
|
async def get_hepa_uv_state(self) -> Optional[HepaUVState]:
|
|
3012
3134
|
return await self._backend.get_hepa_uv_state()
|
|
3135
|
+
|
|
3136
|
+
async def increase_evo_disp_count(
|
|
3137
|
+
self,
|
|
3138
|
+
mount: Union[top_types.Mount, OT3Mount],
|
|
3139
|
+
) -> None:
|
|
3140
|
+
"""Tell a pipette to increase its evo-tip-dispense-count in eeprom."""
|
|
3141
|
+
realmount = OT3Mount.from_mount(mount)
|
|
3142
|
+
await self._backend.increase_evo_disp_count(realmount)
|
|
@@ -2,7 +2,7 @@ from typing import Optional
|
|
|
2
2
|
from typing_extensions import Protocol
|
|
3
3
|
|
|
4
4
|
from opentrons.types import Point
|
|
5
|
-
from opentrons.hardware_control.types import CriticalPoint
|
|
5
|
+
from opentrons.hardware_control.types import CriticalPoint, TipScrapeType
|
|
6
6
|
from .types import MountArgType, CalibrationType, ConfigType
|
|
7
7
|
|
|
8
8
|
from .instrument_configurer import InstrumentConfigurer
|
|
@@ -98,6 +98,7 @@ class LiquidHandler(
|
|
|
98
98
|
mount: MountArgType,
|
|
99
99
|
volume: Optional[float] = None,
|
|
100
100
|
rate: float = 1.0,
|
|
101
|
+
correction_volume: float = 0.0,
|
|
101
102
|
) -> None:
|
|
102
103
|
"""
|
|
103
104
|
Aspirate a volume of liquid (in microliters/uL) using this pipette
|
|
@@ -117,6 +118,24 @@ class LiquidHandler(
|
|
|
117
118
|
volume : [float] The number of microliters to aspirate
|
|
118
119
|
rate : [float] Set plunger speed for this aspirate, where
|
|
119
120
|
speed = rate * aspirate_speed
|
|
121
|
+
correction_volume : Correction volume in uL for the specified aspirate volume
|
|
122
|
+
"""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
async def aspirate_while_tracking(
|
|
126
|
+
self,
|
|
127
|
+
mount: MountArgType,
|
|
128
|
+
z_distance: float,
|
|
129
|
+
volume: float,
|
|
130
|
+
flow_rate: float = 1.0,
|
|
131
|
+
) -> None:
|
|
132
|
+
"""
|
|
133
|
+
Aspirate a volume of liquid (in microliters/uL) while moving the z axis synchronously.
|
|
134
|
+
|
|
135
|
+
:param mount: A robot mount that the instrument is on.
|
|
136
|
+
:param z_distance: The distance the z axis will move during apsiration.
|
|
137
|
+
:param volume: The volume of liquid to be aspirated.
|
|
138
|
+
:param flow_rate: The flow rate to aspirate with.
|
|
120
139
|
"""
|
|
121
140
|
...
|
|
122
141
|
|
|
@@ -126,6 +145,8 @@ class LiquidHandler(
|
|
|
126
145
|
volume: Optional[float] = None,
|
|
127
146
|
rate: float = 1.0,
|
|
128
147
|
push_out: Optional[float] = None,
|
|
148
|
+
correction_volume: float = 0.0,
|
|
149
|
+
is_full_dispense: bool = False,
|
|
129
150
|
) -> None:
|
|
130
151
|
"""
|
|
131
152
|
Dispense a volume of liquid in microliters(uL) using this pipette
|
|
@@ -136,6 +157,26 @@ class LiquidHandler(
|
|
|
136
157
|
volume : [float] The number of microliters to dispense
|
|
137
158
|
rate : [float] Set plunger speed for this dispense, where
|
|
138
159
|
speed = rate * dispense_speed
|
|
160
|
+
correction_volume : Correction volume in uL for the specified dispense volume
|
|
161
|
+
"""
|
|
162
|
+
...
|
|
163
|
+
|
|
164
|
+
async def dispense_while_tracking(
|
|
165
|
+
self,
|
|
166
|
+
mount: MountArgType,
|
|
167
|
+
z_distance: float,
|
|
168
|
+
volume: float,
|
|
169
|
+
push_out: Optional[float],
|
|
170
|
+
flow_rate: float = 1.0,
|
|
171
|
+
is_full_dispense: bool = False,
|
|
172
|
+
) -> None:
|
|
173
|
+
"""
|
|
174
|
+
Dispense a volume of liquid (in microliters/uL) while moving the z axis synchronously.
|
|
175
|
+
|
|
176
|
+
:param mount: A robot mount that the instrument is on.
|
|
177
|
+
:param z_distance: The distance the z axis will move during dispensing.
|
|
178
|
+
:param volume: The volume of liquid to be dispensed.
|
|
179
|
+
:param flow_rate: The flow rate to dispense with.
|
|
139
180
|
"""
|
|
140
181
|
...
|
|
141
182
|
|
|
@@ -187,6 +228,7 @@ class LiquidHandler(
|
|
|
187
228
|
mount: MountArgType,
|
|
188
229
|
home_after: bool = True,
|
|
189
230
|
ignore_plunger: bool = False,
|
|
231
|
+
scrape_type: TipScrapeType = TipScrapeType.NONE,
|
|
190
232
|
) -> None:
|
|
191
233
|
...
|
|
192
234
|
|
|
@@ -218,3 +260,7 @@ class LiquidHandler(
|
|
|
218
260
|
max_z_dist : maximum depth to probe for liquid
|
|
219
261
|
"""
|
|
220
262
|
...
|
|
263
|
+
|
|
264
|
+
async def increase_evo_disp_count(self, mount: MountArgType) -> None:
|
|
265
|
+
"""Tell a pipette to increase it's evo-tip-dispense-count in eeprom."""
|
|
266
|
+
...
|
|
@@ -636,6 +636,12 @@ class InstrumentProbeType(enum.Enum):
|
|
|
636
636
|
BOTH = enum.auto()
|
|
637
637
|
|
|
638
638
|
|
|
639
|
+
class TipScrapeType(enum.Enum):
|
|
640
|
+
NONE = enum.auto()
|
|
641
|
+
RIGHT_ONE_COL = enum.auto()
|
|
642
|
+
LEFT_ONE_COL = enum.auto()
|
|
643
|
+
|
|
644
|
+
|
|
639
645
|
class GripperProbe(enum.Enum):
|
|
640
646
|
FRONT = enum.auto()
|
|
641
647
|
REAR = enum.auto()
|