opentrons 8.1.0__py2.py3-none-any.whl → 8.2.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/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 +208 -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/backends/ot3utils.py +1 -0
- 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 +232 -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 +131 -94
- 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/hardware_control/protocols/position_estimator.py +3 -1
- opentrons/hardware_control/types.py +2 -0
- opentrons/legacy_commands/helpers.py +8 -2
- 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 +20 -4
- opentrons/protocol_api/core/engine/module_core.py +166 -17
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +362 -0
- opentrons/protocol_api/core/engine/protocol.py +30 -2
- opentrons/protocol_api/core/instrument.py +2 -0
- opentrons/protocol_api/core/labware.py +4 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +6 -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 +6 -2
- opentrons/protocol_api/instrument_context.py +52 -20
- opentrons/protocol_api/labware.py +13 -1
- opentrons/protocol_api/module_contexts.py +115 -17
- opentrons/protocol_api/protocol_context.py +49 -5
- opentrons/protocol_api/validation.py +5 -3
- opentrons/protocol_engine/__init__.py +10 -9
- opentrons/protocol_engine/actions/__init__.py +3 -0
- opentrons/protocol_engine/actions/actions.py +30 -25
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/sync_client.py +1 -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 +148 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +65 -9
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +148 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +200 -0
- opentrons/protocol_engine/commands/aspirate.py +29 -16
- opentrons/protocol_engine/commands/aspirate_in_place.py +33 -16
- 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 +31 -18
- 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 +70 -16
- opentrons/protocol_engine/commands/drop_tip_in_place.py +59 -13
- 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 +28 -5
- opentrons/protocol_engine/commands/load_liquid.py +18 -7
- opentrons/protocol_engine/commands/load_module.py +4 -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 +155 -23
- 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 +51 -30
- opentrons/protocol_engine/commands/pipetting_common.py +47 -16
- 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 +208 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +10 -4
- 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 +60 -10
- opentrons/protocol_engine/engine_support.py +2 -1
- opentrons/protocol_engine/error_recovery_policy.py +14 -3
- opentrons/protocol_engine/errors/__init__.py +20 -0
- opentrons/protocol_engine/errors/error_occurrence.py +8 -3
- opentrons/protocol_engine/errors/exceptions.py +127 -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 +73 -22
- opentrons/protocol_engine/execution/movement.py +17 -7
- 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 +47 -31
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +19 -5
- opentrons/protocol_engine/resources/file_provider.py +161 -0
- opentrons/protocol_engine/resources/fixture_validation.py +11 -1
- 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 +445 -59
- opentrons/protocol_engine/state/labware.py +264 -84
- opentrons/protocol_engine/state/liquids.py +1 -1
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +21 -3
- opentrons/protocol_engine/state/modules.py +145 -90
- opentrons/protocol_engine/state/motion.py +33 -14
- 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 +424 -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 +41 -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/protocols/parameters/csv_parameter_interface.py +3 -1
- opentrons/simulate.py +3 -3
- opentrons/types.py +30 -3
- opentrons/util/logging_config.py +34 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/METADATA +5 -4
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/RECORD +235 -223
- 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.0.dist-info → opentrons-8.2.0.dist-info}/LICENSE +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/WHEEL +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
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
|
+
from opentrons.protocol_engine.types import AddressableAreaLocation
|
|
12
|
+
|
|
13
|
+
from ...state.update_types import StateUpdate
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from opentrons.drivers.types import AbsorbanceReaderLidStatus
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from opentrons.protocol_engine.state.state import StateView
|
|
20
|
+
from opentrons.protocol_engine.execution import (
|
|
21
|
+
EquipmentHandler,
|
|
22
|
+
LabwareMovementHandler,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
CloseLidCommandType = Literal["absorbanceReader/closeLid"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CloseLidParams(BaseModel):
|
|
30
|
+
"""Input parameters to close the lid on an absorbance reading."""
|
|
31
|
+
|
|
32
|
+
moduleId: str = Field(..., description="Unique ID of the absorbance reader.")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CloseLidResult(BaseModel):
|
|
36
|
+
"""Result data from closing the lid on an aborbance reading."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CloseLidImpl(AbstractCommandImpl[CloseLidParams, SuccessData[CloseLidResult]]):
|
|
40
|
+
"""Execution implementation of closing the lid on an Absorbance Reader."""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
state_view: StateView,
|
|
45
|
+
equipment: EquipmentHandler,
|
|
46
|
+
labware_movement: LabwareMovementHandler,
|
|
47
|
+
**unused_dependencies: object,
|
|
48
|
+
) -> None:
|
|
49
|
+
self._state_view = state_view
|
|
50
|
+
self._equipment = equipment
|
|
51
|
+
self._labware_movement = labware_movement
|
|
52
|
+
|
|
53
|
+
async def execute(self, params: CloseLidParams) -> SuccessData[CloseLidResult]:
|
|
54
|
+
"""Execute the close lid command."""
|
|
55
|
+
state_update = StateUpdate()
|
|
56
|
+
mod_substate = self._state_view.modules.get_absorbance_reader_substate(
|
|
57
|
+
module_id=params.moduleId
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
hardware_lid_status = AbsorbanceReaderLidStatus.OFF
|
|
61
|
+
if not self._state_view.config.use_virtual_modules:
|
|
62
|
+
abs_reader = self._equipment.get_module_hardware_api(mod_substate.module_id)
|
|
63
|
+
|
|
64
|
+
if abs_reader is not None:
|
|
65
|
+
hardware_lid_status = await abs_reader.get_current_lid_status()
|
|
66
|
+
else:
|
|
67
|
+
raise CannotPerformModuleAction(
|
|
68
|
+
"Could not reach the Hardware API for Opentrons Plate Reader Module."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if hardware_lid_status is AbsorbanceReaderLidStatus.ON:
|
|
72
|
+
# The lid is already physically ON, so we can no-op physically closing it
|
|
73
|
+
state_update.set_absorbance_reader_lid(
|
|
74
|
+
module_id=mod_substate.module_id, is_lid_on=True
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
# Allow propagation of ModuleNotAttachedError.
|
|
78
|
+
_ = self._equipment.get_module_hardware_api(mod_substate.module_id)
|
|
79
|
+
|
|
80
|
+
lid_definition = (
|
|
81
|
+
self._state_view.labware.get_absorbance_reader_lid_definition()
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
current_location = self._state_view.modules.absorbance_reader_dock_location(
|
|
85
|
+
params.moduleId
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# we need to move the lid onto the module reader
|
|
89
|
+
absorbance_model = self._state_view.modules.get_requested_model(
|
|
90
|
+
params.moduleId
|
|
91
|
+
)
|
|
92
|
+
assert absorbance_model is not None
|
|
93
|
+
new_location = AddressableAreaLocation(
|
|
94
|
+
addressableAreaName=self._state_view.modules.ensure_and_convert_module_fixture_location(
|
|
95
|
+
deck_slot=self._state_view.modules.get_location(
|
|
96
|
+
params.moduleId
|
|
97
|
+
).slotName,
|
|
98
|
+
deck_type=self._state_view.config.deck_type,
|
|
99
|
+
model=absorbance_model,
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# The lid's labware definition stores gripper offsets for itself in the
|
|
104
|
+
# space normally meant for offsets for labware stacked atop it.
|
|
105
|
+
lid_gripper_offsets = self._state_view.labware.get_child_gripper_offsets(
|
|
106
|
+
labware_definition=lid_definition,
|
|
107
|
+
slot_name=None,
|
|
108
|
+
)
|
|
109
|
+
if lid_gripper_offsets is None:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
"Gripper Offset values for Absorbance Reader Lid labware must not be None."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
await self._labware_movement.move_labware_with_gripper(
|
|
115
|
+
labware_definition=lid_definition,
|
|
116
|
+
current_location=current_location,
|
|
117
|
+
new_location=new_location,
|
|
118
|
+
user_offset_data=lid_gripper_offsets,
|
|
119
|
+
post_drop_slide_offset=None,
|
|
120
|
+
)
|
|
121
|
+
state_update.set_absorbance_reader_lid(
|
|
122
|
+
module_id=mod_substate.module_id,
|
|
123
|
+
is_lid_on=True,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return SuccessData(
|
|
127
|
+
public=CloseLidResult(),
|
|
128
|
+
state_update=state_update,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class CloseLid(BaseCommand[CloseLidParams, CloseLidResult, ErrorOccurrence]):
|
|
133
|
+
"""A command to close the lid on an Absorbance Reader."""
|
|
134
|
+
|
|
135
|
+
commandType: CloseLidCommandType = "absorbanceReader/closeLid"
|
|
136
|
+
params: CloseLidParams
|
|
137
|
+
result: Optional[CloseLidResult]
|
|
138
|
+
|
|
139
|
+
_ImplementationCls: Type[CloseLidImpl] = CloseLidImpl
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class CloseLidCreate(BaseCommandCreate[CloseLidParams]):
|
|
143
|
+
"""A request to execute an Absorbance Reader close lid command."""
|
|
144
|
+
|
|
145
|
+
commandType: CloseLidCommandType = "absorbanceReader/closeLid"
|
|
146
|
+
params: CloseLidParams
|
|
147
|
+
|
|
148
|
+
_CommandCls: Type[CloseLid] = CloseLid
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
"""Command models to initialize an Absorbance Reader."""
|
|
2
2
|
from __future__ import annotations
|
|
3
|
-
from typing import Optional, Literal, TYPE_CHECKING
|
|
3
|
+
from typing import List, Optional, Literal, TYPE_CHECKING
|
|
4
4
|
from typing_extensions import Type
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel, Field
|
|
7
7
|
|
|
8
|
+
from opentrons.drivers.types import ABSMeasurementMode
|
|
9
|
+
from opentrons.protocol_engine.types import ABSMeasureMode
|
|
10
|
+
|
|
8
11
|
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
9
12
|
from ...errors.error_occurrence import ErrorOccurrence
|
|
13
|
+
from ...errors import InvalidWavelengthError
|
|
10
14
|
|
|
11
15
|
if TYPE_CHECKING:
|
|
12
|
-
from opentrons.protocol_engine.state import StateView
|
|
16
|
+
from opentrons.protocol_engine.state.state import StateView
|
|
13
17
|
from opentrons.protocol_engine.execution import EquipmentHandler
|
|
14
18
|
|
|
15
19
|
|
|
@@ -20,7 +24,13 @@ class InitializeParams(BaseModel):
|
|
|
20
24
|
"""Input parameters to initialize an absorbance reading."""
|
|
21
25
|
|
|
22
26
|
moduleId: str = Field(..., description="Unique ID of the absorbance reader.")
|
|
23
|
-
|
|
27
|
+
measureMode: ABSMeasureMode = Field(
|
|
28
|
+
..., description="Initialize single or multi measurement mode."
|
|
29
|
+
)
|
|
30
|
+
sampleWavelengths: List[int] = Field(..., description="Sample wavelengths in nm.")
|
|
31
|
+
referenceWavelength: Optional[int] = Field(
|
|
32
|
+
None, description="Optional reference wavelength in nm."
|
|
33
|
+
)
|
|
24
34
|
|
|
25
35
|
|
|
26
36
|
class InitializeResult(BaseModel):
|
|
@@ -28,7 +38,7 @@ class InitializeResult(BaseModel):
|
|
|
28
38
|
|
|
29
39
|
|
|
30
40
|
class InitializeImpl(
|
|
31
|
-
AbstractCommandImpl[InitializeParams, SuccessData[InitializeResult
|
|
41
|
+
AbstractCommandImpl[InitializeParams, SuccessData[InitializeResult]]
|
|
32
42
|
):
|
|
33
43
|
"""Execution implementation of initializing an Absorbance Reader."""
|
|
34
44
|
|
|
@@ -41,9 +51,7 @@ class InitializeImpl(
|
|
|
41
51
|
self._state_view = state_view
|
|
42
52
|
self._equipment = equipment
|
|
43
53
|
|
|
44
|
-
async def execute(
|
|
45
|
-
self, params: InitializeParams
|
|
46
|
-
) -> SuccessData[InitializeResult, None]:
|
|
54
|
+
async def execute(self, params: InitializeParams) -> SuccessData[InitializeResult]:
|
|
47
55
|
"""Initiate a single absorbance measurement."""
|
|
48
56
|
abs_reader_substate = self._state_view.modules.get_absorbance_reader_substate(
|
|
49
57
|
module_id=params.moduleId
|
|
@@ -54,11 +62,59 @@ class InitializeImpl(
|
|
|
54
62
|
)
|
|
55
63
|
|
|
56
64
|
if abs_reader is not None:
|
|
57
|
-
|
|
65
|
+
# Validate the parameters before initializing.
|
|
66
|
+
sample_wavelengths = set(params.sampleWavelengths)
|
|
67
|
+
sample_wavelengths_len = len(params.sampleWavelengths)
|
|
68
|
+
reference_wavelength = params.referenceWavelength
|
|
69
|
+
supported_wavelengths = set(abs_reader.supported_wavelengths)
|
|
70
|
+
unsupported_wavelengths = sample_wavelengths.difference(
|
|
71
|
+
supported_wavelengths
|
|
72
|
+
)
|
|
73
|
+
sample_wl_str = ", ".join([str(w) + "nm" for w in sample_wavelengths])
|
|
74
|
+
supported_wl_str = ", ".join([str(w) + "nm" for w in supported_wavelengths])
|
|
75
|
+
unsupported_wl_str = ", ".join(
|
|
76
|
+
[str(w) + "nm" for w in unsupported_wavelengths]
|
|
77
|
+
)
|
|
78
|
+
if unsupported_wavelengths:
|
|
79
|
+
raise InvalidWavelengthError(
|
|
80
|
+
f"Unsupported wavelengths: {unsupported_wl_str}. "
|
|
81
|
+
f" Use one of {supported_wl_str} instead."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if params.measureMode == "single":
|
|
85
|
+
if sample_wavelengths_len != 1:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"Measure mode `single` requires one sample wavelength,"
|
|
88
|
+
f" {sample_wl_str} provided instead."
|
|
89
|
+
)
|
|
90
|
+
if (
|
|
91
|
+
reference_wavelength is not None
|
|
92
|
+
and reference_wavelength not in supported_wavelengths
|
|
93
|
+
):
|
|
94
|
+
raise InvalidWavelengthError(
|
|
95
|
+
f"Reference wavelength {reference_wavelength}nm is not supported."
|
|
96
|
+
f" Use one of {supported_wl_str} instead."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if params.measureMode == "multi":
|
|
100
|
+
if sample_wavelengths_len < 1 or sample_wavelengths_len > 6:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Measure mode `multi` requires 1-6 sample wavelengths,"
|
|
103
|
+
f" {sample_wl_str} provided instead."
|
|
104
|
+
)
|
|
105
|
+
if reference_wavelength is not None:
|
|
106
|
+
raise ValueError(
|
|
107
|
+
"Reference wavelength cannot be used with Measure mode `multi`."
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
await abs_reader.set_sample_wavelength(
|
|
111
|
+
ABSMeasurementMode(params.measureMode),
|
|
112
|
+
params.sampleWavelengths,
|
|
113
|
+
reference_wavelength=params.referenceWavelength,
|
|
114
|
+
)
|
|
58
115
|
|
|
59
116
|
return SuccessData(
|
|
60
117
|
public=InitializeResult(),
|
|
61
|
-
private=None,
|
|
62
118
|
)
|
|
63
119
|
|
|
64
120
|
|
|
@@ -0,0 +1,148 @@
|
|
|
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.types import AddressableAreaLocation
|
|
13
|
+
|
|
14
|
+
from opentrons.drivers.types import AbsorbanceReaderLidStatus
|
|
15
|
+
|
|
16
|
+
from ...state.update_types import StateUpdate
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from opentrons.protocol_engine.state.state import StateView
|
|
21
|
+
from opentrons.protocol_engine.execution import (
|
|
22
|
+
EquipmentHandler,
|
|
23
|
+
LabwareMovementHandler,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
OpenLidCommandType = Literal["absorbanceReader/openLid"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class OpenLidParams(BaseModel):
|
|
31
|
+
"""Input parameters to open the lid on an absorbance reading."""
|
|
32
|
+
|
|
33
|
+
moduleId: str = Field(..., description="Unique ID of the absorbance reader.")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OpenLidResult(BaseModel):
|
|
37
|
+
"""Result data from opening the lid on an aborbance reading."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class OpenLidImpl(AbstractCommandImpl[OpenLidParams, SuccessData[OpenLidResult]]):
|
|
41
|
+
"""Execution implementation of opening the lid on an Absorbance Reader."""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
state_view: StateView,
|
|
46
|
+
equipment: EquipmentHandler,
|
|
47
|
+
labware_movement: LabwareMovementHandler,
|
|
48
|
+
**unused_dependencies: object,
|
|
49
|
+
) -> None:
|
|
50
|
+
self._state_view = state_view
|
|
51
|
+
self._equipment = equipment
|
|
52
|
+
self._labware_movement = labware_movement
|
|
53
|
+
|
|
54
|
+
async def execute(self, params: OpenLidParams) -> SuccessData[OpenLidResult]:
|
|
55
|
+
"""Move the absorbance reader lid from the module to the lid dock."""
|
|
56
|
+
state_update = StateUpdate()
|
|
57
|
+
mod_substate = self._state_view.modules.get_absorbance_reader_substate(
|
|
58
|
+
module_id=params.moduleId
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
hardware_lid_status = AbsorbanceReaderLidStatus.ON
|
|
62
|
+
if not self._state_view.config.use_virtual_modules:
|
|
63
|
+
abs_reader = self._equipment.get_module_hardware_api(mod_substate.module_id)
|
|
64
|
+
|
|
65
|
+
if abs_reader is not None:
|
|
66
|
+
hardware_lid_status = await abs_reader.get_current_lid_status()
|
|
67
|
+
else:
|
|
68
|
+
raise CannotPerformModuleAction(
|
|
69
|
+
"Could not reach the Hardware API for Opentrons Plate Reader Module."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if hardware_lid_status is AbsorbanceReaderLidStatus.OFF:
|
|
73
|
+
# The lid is already physically OFF, so we can no-op physically closing it
|
|
74
|
+
state_update.set_absorbance_reader_lid(
|
|
75
|
+
module_id=mod_substate.module_id, is_lid_on=False
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
# Allow propagation of ModuleNotAttachedError.
|
|
79
|
+
_ = self._equipment.get_module_hardware_api(mod_substate.module_id)
|
|
80
|
+
|
|
81
|
+
lid_definition = (
|
|
82
|
+
self._state_view.labware.get_absorbance_reader_lid_definition()
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
absorbance_model = self._state_view.modules.get_requested_model(
|
|
86
|
+
params.moduleId
|
|
87
|
+
)
|
|
88
|
+
assert absorbance_model is not None
|
|
89
|
+
current_location = AddressableAreaLocation(
|
|
90
|
+
addressableAreaName=self._state_view.modules.ensure_and_convert_module_fixture_location(
|
|
91
|
+
deck_slot=self._state_view.modules.get_location(
|
|
92
|
+
params.moduleId
|
|
93
|
+
).slotName,
|
|
94
|
+
deck_type=self._state_view.config.deck_type,
|
|
95
|
+
model=absorbance_model,
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# we need to move the lid to the lid dock
|
|
100
|
+
new_location = self._state_view.modules.absorbance_reader_dock_location(
|
|
101
|
+
mod_substate.module_id
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# The lid's labware definition stores gripper offsets for itself in the
|
|
105
|
+
# space normally meant for offsets for labware stacked atop it.
|
|
106
|
+
lid_gripper_offsets = self._state_view.labware.get_child_gripper_offsets(
|
|
107
|
+
labware_definition=lid_definition,
|
|
108
|
+
slot_name=None,
|
|
109
|
+
)
|
|
110
|
+
if lid_gripper_offsets is None:
|
|
111
|
+
raise ValueError(
|
|
112
|
+
"Gripper Offset values for Absorbance Reader Lid labware must not be None."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
await self._labware_movement.move_labware_with_gripper(
|
|
116
|
+
labware_definition=lid_definition,
|
|
117
|
+
current_location=current_location,
|
|
118
|
+
new_location=new_location,
|
|
119
|
+
user_offset_data=lid_gripper_offsets,
|
|
120
|
+
post_drop_slide_offset=None,
|
|
121
|
+
)
|
|
122
|
+
state_update.set_absorbance_reader_lid(
|
|
123
|
+
module_id=mod_substate.module_id, is_lid_on=False
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return SuccessData(
|
|
127
|
+
public=OpenLidResult(),
|
|
128
|
+
state_update=state_update,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class OpenLid(BaseCommand[OpenLidParams, OpenLidResult, ErrorOccurrence]):
|
|
133
|
+
"""A command to open the lid on an Absorbance Reader."""
|
|
134
|
+
|
|
135
|
+
commandType: OpenLidCommandType = "absorbanceReader/openLid"
|
|
136
|
+
params: OpenLidParams
|
|
137
|
+
result: Optional[OpenLidResult]
|
|
138
|
+
|
|
139
|
+
_ImplementationCls: Type[OpenLidImpl] = OpenLidImpl
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class OpenLidCreate(BaseCommandCreate[OpenLidParams]):
|
|
143
|
+
"""A request to execute an Absorbance Reader open lid command."""
|
|
144
|
+
|
|
145
|
+
commandType: OpenLidCommandType = "absorbanceReader/openLid"
|
|
146
|
+
params: OpenLidParams
|
|
147
|
+
|
|
148
|
+
_CommandCls: Type[OpenLid] = OpenLid
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
if abs_reader_substate.is_lid_on is False:
|
|
84
|
+
raise CannotPerformModuleAction(
|
|
85
|
+
"Absorbance Plate Reader can't read a plate with the lid open. Call `close_lid()` first."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# TODO: we need to return a file ID and increase the file count even when a moduel is not attached
|
|
89
|
+
if (
|
|
90
|
+
params.fileName is not None
|
|
91
|
+
and abs_reader_substate.configured_wavelengths is not None
|
|
92
|
+
):
|
|
93
|
+
# Validate that the amount of files we are about to generate does not put us higher than the limit
|
|
94
|
+
if (
|
|
95
|
+
self._state_view.files.get_filecount()
|
|
96
|
+
+ len(abs_reader_substate.configured_wavelengths)
|
|
97
|
+
> MAXIMUM_CSV_FILE_LIMIT
|
|
98
|
+
):
|
|
99
|
+
raise StorageLimitReachedError(
|
|
100
|
+
message=f"Attempt to write file {params.fileName} exceeds file creation limit of {MAXIMUM_CSV_FILE_LIMIT} files."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
asbsorbance_result: Dict[int, Dict[str, float]] = {}
|
|
104
|
+
transform_results = []
|
|
105
|
+
# Handle the measurement and begin building data for return
|
|
106
|
+
if abs_reader is not None:
|
|
107
|
+
start_time = datetime.now()
|
|
108
|
+
results = await abs_reader.start_measure()
|
|
109
|
+
finish_time = datetime.now()
|
|
110
|
+
if abs_reader._measurement_config is not None:
|
|
111
|
+
sample_wavelengths = abs_reader._measurement_config.sample_wavelengths
|
|
112
|
+
for wavelength, result in zip(sample_wavelengths, results):
|
|
113
|
+
converted_values = (
|
|
114
|
+
self._state_view.modules.convert_absorbance_reader_data_points(
|
|
115
|
+
data=result
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
asbsorbance_result[wavelength] = converted_values
|
|
119
|
+
transform_results.append(
|
|
120
|
+
ReadData.construct(wavelength=wavelength, data=converted_values)
|
|
121
|
+
)
|
|
122
|
+
# Handle the virtual module case for data creation (all zeroes)
|
|
123
|
+
elif self._state_view.config.use_virtual_modules:
|
|
124
|
+
start_time = finish_time = datetime.now()
|
|
125
|
+
if abs_reader_substate.configured_wavelengths is not None:
|
|
126
|
+
for wavelength in abs_reader_substate.configured_wavelengths:
|
|
127
|
+
converted_values = (
|
|
128
|
+
self._state_view.modules.convert_absorbance_reader_data_points(
|
|
129
|
+
data=[0] * 96
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
asbsorbance_result[wavelength] = converted_values
|
|
133
|
+
transform_results.append(
|
|
134
|
+
ReadData.construct(wavelength=wavelength, data=converted_values)
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
raise CannotPerformModuleAction(
|
|
138
|
+
"Plate Reader data cannot be requested with a module that has not been initialized."
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# TODO (cb, 10-17-2024): FILE PROVIDER - Some day we may want to break the file provider behavior into a seperate API function.
|
|
142
|
+
# When this happens, we probably will to have the change the command results handler we utilize to track file IDs in engine.
|
|
143
|
+
# Today, the action handler for the FileStore looks for a ReadAbsorbanceResult command action, this will need to be delinked.
|
|
144
|
+
|
|
145
|
+
# Begin interfacing with the file provider if the user provided a filename
|
|
146
|
+
file_ids = []
|
|
147
|
+
if params.fileName is not None:
|
|
148
|
+
# Create the Plate Reader Transform
|
|
149
|
+
plate_read_result = PlateReaderData.construct(
|
|
150
|
+
read_results=transform_results,
|
|
151
|
+
reference_wavelength=abs_reader_substate.reference_wavelength,
|
|
152
|
+
start_time=start_time,
|
|
153
|
+
finish_time=finish_time,
|
|
154
|
+
serial_number=abs_reader.serial_number
|
|
155
|
+
if (abs_reader is not None and abs_reader.serial_number is not None)
|
|
156
|
+
else "VIRTUAL_SERIAL",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if isinstance(plate_read_result, PlateReaderData):
|
|
160
|
+
# Write a CSV file for each of the measurements taken
|
|
161
|
+
for measurement in plate_read_result.read_results:
|
|
162
|
+
file_id = await self._file_provider.write_csv(
|
|
163
|
+
write_data=plate_read_result.build_generic_csv(
|
|
164
|
+
filename=params.fileName,
|
|
165
|
+
measurement=measurement,
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
file_ids.append(file_id)
|
|
169
|
+
|
|
170
|
+
# Return success data to api
|
|
171
|
+
return SuccessData(
|
|
172
|
+
public=ReadAbsorbanceResult(
|
|
173
|
+
data=asbsorbance_result, fileIds=file_ids
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return SuccessData(
|
|
178
|
+
public=ReadAbsorbanceResult(data=asbsorbance_result, fileIds=file_ids),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class ReadAbsorbance(
|
|
183
|
+
BaseCommand[ReadAbsorbanceParams, ReadAbsorbanceResult, ErrorOccurrence]
|
|
184
|
+
):
|
|
185
|
+
"""A command to execute an Absorbance Reader measurement."""
|
|
186
|
+
|
|
187
|
+
commandType: ReadAbsorbanceCommandType = "absorbanceReader/read"
|
|
188
|
+
params: ReadAbsorbanceParams
|
|
189
|
+
result: Optional[ReadAbsorbanceResult]
|
|
190
|
+
|
|
191
|
+
_ImplementationCls: Type[ReadAbsorbanceImpl] = ReadAbsorbanceImpl
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class ReadAbsorbanceCreate(BaseCommandCreate[ReadAbsorbanceParams]):
|
|
195
|
+
"""A request to execute an Absorbance Reader measurement."""
|
|
196
|
+
|
|
197
|
+
commandType: ReadAbsorbanceCommandType = "absorbanceReader/read"
|
|
198
|
+
params: ReadAbsorbanceParams
|
|
199
|
+
|
|
200
|
+
_CommandCls: Type[ReadAbsorbance] = ReadAbsorbance
|