opentrons 8.7.0a9__py3-none-any.whl → 8.8.0a8__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/_version.py +2 -2
- opentrons/cli/analyze.py +4 -1
- opentrons/config/__init__.py +7 -0
- opentrons/drivers/asyncio/communication/serial_connection.py +126 -49
- opentrons/drivers/heater_shaker/abstract.py +5 -0
- opentrons/drivers/heater_shaker/driver.py +10 -0
- opentrons/drivers/heater_shaker/simulator.py +4 -0
- opentrons/drivers/thermocycler/abstract.py +6 -0
- opentrons/drivers/thermocycler/driver.py +61 -10
- opentrons/drivers/thermocycler/simulator.py +6 -0
- opentrons/drivers/vacuum_module/__init__.py +5 -0
- opentrons/drivers/vacuum_module/abstract.py +93 -0
- opentrons/drivers/vacuum_module/driver.py +208 -0
- opentrons/drivers/vacuum_module/errors.py +39 -0
- opentrons/drivers/vacuum_module/simulator.py +85 -0
- opentrons/drivers/vacuum_module/types.py +79 -0
- opentrons/execute.py +3 -0
- opentrons/hardware_control/api.py +24 -5
- opentrons/hardware_control/backends/controller.py +8 -2
- opentrons/hardware_control/backends/flex_protocol.py +1 -0
- opentrons/hardware_control/backends/ot3controller.py +35 -2
- opentrons/hardware_control/backends/ot3simulator.py +3 -1
- opentrons/hardware_control/backends/ot3utils.py +37 -0
- opentrons/hardware_control/backends/simulator.py +2 -1
- opentrons/hardware_control/backends/subsystem_manager.py +5 -2
- opentrons/hardware_control/emulation/abstract_emulator.py +6 -4
- opentrons/hardware_control/emulation/connection_handler.py +8 -5
- opentrons/hardware_control/emulation/heater_shaker.py +12 -3
- opentrons/hardware_control/emulation/settings.py +1 -1
- opentrons/hardware_control/emulation/thermocycler.py +67 -15
- opentrons/hardware_control/module_control.py +105 -10
- opentrons/hardware_control/modules/__init__.py +3 -0
- opentrons/hardware_control/modules/absorbance_reader.py +11 -4
- opentrons/hardware_control/modules/flex_stacker.py +38 -9
- opentrons/hardware_control/modules/heater_shaker.py +42 -5
- opentrons/hardware_control/modules/magdeck.py +8 -4
- opentrons/hardware_control/modules/mod_abc.py +14 -6
- opentrons/hardware_control/modules/tempdeck.py +25 -5
- opentrons/hardware_control/modules/thermocycler.py +68 -11
- opentrons/hardware_control/modules/types.py +20 -1
- opentrons/hardware_control/modules/utils.py +11 -4
- opentrons/hardware_control/motion_utilities.py +6 -6
- opentrons/hardware_control/nozzle_manager.py +3 -0
- opentrons/hardware_control/ot3api.py +92 -17
- opentrons/hardware_control/poller.py +22 -8
- opentrons/hardware_control/protocols/liquid_handler.py +12 -4
- opentrons/hardware_control/scripts/update_module_fw.py +5 -0
- opentrons/hardware_control/types.py +43 -2
- opentrons/legacy_commands/commands.py +58 -5
- opentrons/legacy_commands/module_commands.py +52 -0
- opentrons/legacy_commands/protocol_commands.py +53 -1
- opentrons/legacy_commands/types.py +155 -1
- opentrons/motion_planning/deck_conflict.py +17 -12
- opentrons/motion_planning/waypoints.py +15 -29
- opentrons/protocol_api/__init__.py +5 -1
- opentrons/protocol_api/_transfer_liquid_validation.py +17 -2
- opentrons/protocol_api/_types.py +8 -1
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/_default_labware_versions.py +33 -11
- opentrons/protocol_api/core/engine/deck_conflict.py +3 -1
- opentrons/protocol_api/core/engine/instrument.py +109 -26
- opentrons/protocol_api/core/engine/labware.py +8 -1
- opentrons/protocol_api/core/engine/module_core.py +95 -4
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +4 -18
- opentrons/protocol_api/core/engine/protocol.py +51 -2
- opentrons/protocol_api/core/engine/stringify.py +2 -0
- opentrons/protocol_api/core/engine/tasks.py +48 -0
- opentrons/protocol_api/core/engine/well.py +8 -0
- opentrons/protocol_api/core/instrument.py +19 -2
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +19 -2
- opentrons/protocol_api/core/legacy/legacy_module_core.py +33 -2
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +23 -1
- opentrons/protocol_api/core/legacy/legacy_well_core.py +4 -0
- opentrons/protocol_api/core/legacy/tasks.py +19 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +19 -2
- opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
- opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
- opentrons/protocol_api/core/module.py +58 -2
- opentrons/protocol_api/core/protocol.py +23 -2
- opentrons/protocol_api/core/tasks.py +31 -0
- opentrons/protocol_api/core/well.py +4 -0
- opentrons/protocol_api/instrument_context.py +388 -2
- opentrons/protocol_api/labware.py +10 -2
- opentrons/protocol_api/module_contexts.py +170 -6
- opentrons/protocol_api/protocol_context.py +87 -21
- opentrons/protocol_api/robot_context.py +41 -25
- opentrons/protocol_api/tasks.py +48 -0
- opentrons/protocol_api/validation.py +49 -3
- opentrons/protocol_engine/__init__.py +4 -0
- opentrons/protocol_engine/actions/__init__.py +6 -2
- opentrons/protocol_engine/actions/actions.py +31 -9
- opentrons/protocol_engine/clients/sync_client.py +42 -7
- opentrons/protocol_engine/commands/__init__.py +56 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
- opentrons/protocol_engine/commands/absorbance_reader/read.py +22 -23
- opentrons/protocol_engine/commands/aspirate.py +1 -0
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +52 -19
- opentrons/protocol_engine/commands/capture_image.py +302 -0
- opentrons/protocol_engine/commands/command.py +2 -0
- opentrons/protocol_engine/commands/command_unions.py +62 -0
- opentrons/protocol_engine/commands/create_timer.py +83 -0
- opentrons/protocol_engine/commands/dispense.py +1 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +56 -19
- opentrons/protocol_engine/commands/drop_tip.py +32 -8
- opentrons/protocol_engine/commands/flex_stacker/common.py +35 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +7 -0
- opentrons/protocol_engine/commands/heater_shaker/__init__.py +14 -0
- opentrons/protocol_engine/commands/heater_shaker/common.py +20 -0
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +5 -4
- opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +136 -0
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +31 -5
- opentrons/protocol_engine/commands/move_labware.py +3 -4
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +1 -1
- opentrons/protocol_engine/commands/movement_common.py +31 -2
- opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
- opentrons/protocol_engine/commands/pipetting_common.py +48 -3
- opentrons/protocol_engine/commands/set_tip_state.py +97 -0
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +38 -7
- opentrons/protocol_engine/commands/thermocycler/__init__.py +16 -0
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +44 -7
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +43 -14
- opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +191 -0
- opentrons/protocol_engine/commands/touch_tip.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
- opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
- opentrons/protocol_engine/create_protocol_engine.py +12 -0
- opentrons/protocol_engine/engine_support.py +3 -0
- opentrons/protocol_engine/errors/__init__.py +12 -0
- opentrons/protocol_engine/errors/exceptions.py +119 -0
- opentrons/protocol_engine/execution/__init__.py +4 -0
- opentrons/protocol_engine/execution/command_executor.py +62 -1
- opentrons/protocol_engine/execution/create_queue_worker.py +9 -2
- opentrons/protocol_engine/execution/labware_movement.py +13 -15
- opentrons/protocol_engine/execution/movement.py +2 -0
- opentrons/protocol_engine/execution/pipetting.py +26 -25
- opentrons/protocol_engine/execution/queue_worker.py +4 -0
- opentrons/protocol_engine/execution/run_control.py +8 -0
- opentrons/protocol_engine/execution/task_handler.py +157 -0
- opentrons/protocol_engine/protocol_engine.py +137 -36
- opentrons/protocol_engine/resources/__init__.py +4 -0
- opentrons/protocol_engine/resources/camera_provider.py +110 -0
- opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
- opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
- opentrons/protocol_engine/resources/file_provider.py +133 -58
- opentrons/protocol_engine/resources/labware_validation.py +10 -6
- opentrons/protocol_engine/slot_standardization.py +2 -0
- opentrons/protocol_engine/state/_well_math.py +60 -18
- opentrons/protocol_engine/state/addressable_areas.py +2 -0
- opentrons/protocol_engine/state/camera.py +54 -0
- opentrons/protocol_engine/state/commands.py +37 -14
- opentrons/protocol_engine/state/geometry.py +276 -379
- opentrons/protocol_engine/state/labware.py +62 -108
- opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
- opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1336 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
- opentrons/protocol_engine/state/modules.py +30 -8
- opentrons/protocol_engine/state/motion.py +60 -18
- opentrons/protocol_engine/state/preconditions.py +59 -0
- opentrons/protocol_engine/state/state.py +44 -0
- opentrons/protocol_engine/state/state_summary.py +4 -0
- opentrons/protocol_engine/state/tasks.py +139 -0
- opentrons/protocol_engine/state/tips.py +177 -258
- opentrons/protocol_engine/state/update_types.py +26 -9
- opentrons/protocol_engine/types/__init__.py +23 -4
- opentrons/protocol_engine/types/command_preconditions.py +18 -0
- opentrons/protocol_engine/types/deck_configuration.py +5 -1
- opentrons/protocol_engine/types/instrument.py +8 -1
- opentrons/protocol_engine/types/labware.py +1 -13
- opentrons/protocol_engine/types/location.py +26 -2
- opentrons/protocol_engine/types/module.py +11 -1
- opentrons/protocol_engine/types/tasks.py +38 -0
- opentrons/protocol_engine/types/tip.py +9 -0
- opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
- opentrons/protocol_runner/protocol_runner.py +14 -1
- opentrons/protocol_runner/run_orchestrator.py +49 -2
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +2 -2
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/types.py +2 -1
- opentrons/simulate.py +51 -15
- opentrons/system/camera.py +334 -4
- opentrons/system/ffmpeg.py +110 -0
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/METADATA +4 -4
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/RECORD +189 -161
- opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/WHEEL +0 -0
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/entry_points.txt +0 -0
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/licenses/LICENSE +0 -0
opentrons/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '8.
|
|
32
|
-
__version_tuple__ = version_tuple = (8,
|
|
31
|
+
__version__ = version = '8.8.0a8'
|
|
32
|
+
__version_tuple__ = version_tuple = (8, 8, 0, 'a8')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
opentrons/cli/analyze.py
CHANGED
|
@@ -60,7 +60,7 @@ from opentrons.protocol_engine import (
|
|
|
60
60
|
StateSummary,
|
|
61
61
|
)
|
|
62
62
|
from opentrons.protocol_engine.protocol_engine import code_in_error_tree
|
|
63
|
-
from opentrons.protocol_engine.types import CommandAnnotation
|
|
63
|
+
from opentrons.protocol_engine.types import CommandAnnotation, CommandPreconditions
|
|
64
64
|
|
|
65
65
|
from opentrons_shared_data.robot.types import RobotType
|
|
66
66
|
|
|
@@ -367,6 +367,7 @@ async def _do_analyze(
|
|
|
367
367
|
),
|
|
368
368
|
parameters=[],
|
|
369
369
|
command_annotations=[],
|
|
370
|
+
command_preconditions=None,
|
|
370
371
|
)
|
|
371
372
|
return analysis
|
|
372
373
|
return await orchestrator.run(deck_configuration=[])
|
|
@@ -465,6 +466,7 @@ async def _analyze( # noqa: C901
|
|
|
465
466
|
liquids=analysis.state_summary.liquids,
|
|
466
467
|
commandAnnotations=analysis.command_annotations,
|
|
467
468
|
liquidClasses=analysis.state_summary.liquidClasses,
|
|
469
|
+
commandPreconditions=analysis.command_preconditions,
|
|
468
470
|
)
|
|
469
471
|
|
|
470
472
|
_call_for_output_of_kind(
|
|
@@ -555,3 +557,4 @@ class AnalyzeResults(BaseModel):
|
|
|
555
557
|
liquidClasses: List[LiquidClassRecordWithId]
|
|
556
558
|
errors: List[ErrorOccurrence]
|
|
557
559
|
commandAnnotations: List[CommandAnnotation]
|
|
560
|
+
commandPreconditions: Optional[CommandPreconditions]
|
opentrons/config/__init__.py
CHANGED
|
@@ -307,6 +307,13 @@ CONFIG_ELEMENTS = (
|
|
|
307
307
|
ConfigElementType.DIR,
|
|
308
308
|
"The dir where performance metrics are stored",
|
|
309
309
|
),
|
|
310
|
+
ConfigElement(
|
|
311
|
+
"live_stream_environment_file",
|
|
312
|
+
"Live Stream Configuration",
|
|
313
|
+
Path("opentrons-live-stream.env"),
|
|
314
|
+
ConfigElementType.FILE,
|
|
315
|
+
"The file storing the Opentrons Live Stream Configuration values.",
|
|
316
|
+
),
|
|
310
317
|
)
|
|
311
318
|
#: The available configuration file elements to modify. All of these can be
|
|
312
319
|
#: changed by editing opentrons.json, where the keys are the name elements,
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Type, Literal, AsyncIterator
|
|
6
6
|
|
|
7
7
|
from opentrons.drivers.command_builder import CommandBuilder
|
|
8
8
|
|
|
@@ -25,7 +25,7 @@ class SerialConnection:
|
|
|
25
25
|
port: str,
|
|
26
26
|
baud_rate: int,
|
|
27
27
|
timeout: float,
|
|
28
|
-
loop:
|
|
28
|
+
loop: asyncio.AbstractEventLoop | None,
|
|
29
29
|
reset_buffer_before_write: bool,
|
|
30
30
|
) -> AsyncSerial:
|
|
31
31
|
return await AsyncSerial.create(
|
|
@@ -43,11 +43,11 @@ class SerialConnection:
|
|
|
43
43
|
baud_rate: int,
|
|
44
44
|
timeout: float,
|
|
45
45
|
ack: str,
|
|
46
|
-
name:
|
|
46
|
+
name: str | None = None,
|
|
47
47
|
retry_wait_time_seconds: float = 0.1,
|
|
48
|
-
loop:
|
|
49
|
-
error_keyword:
|
|
50
|
-
alarm_keyword:
|
|
48
|
+
loop: asyncio.AbstractEventLoop | None = None,
|
|
49
|
+
error_keyword: str | None = None,
|
|
50
|
+
alarm_keyword: str | None = None,
|
|
51
51
|
reset_buffer_before_write: bool = False,
|
|
52
52
|
error_codes: Type[BaseErrorCode] = DefaultErrorCodes,
|
|
53
53
|
) -> "SerialConnection":
|
|
@@ -133,7 +133,7 @@ class SerialConnection:
|
|
|
133
133
|
self._error_codes = error_codes
|
|
134
134
|
|
|
135
135
|
async def send_command(
|
|
136
|
-
self, command: CommandBuilder, retries: int = 0, timeout:
|
|
136
|
+
self, command: CommandBuilder, retries: int = 0, timeout: float | None = None
|
|
137
137
|
) -> str:
|
|
138
138
|
"""
|
|
139
139
|
Send a command and return the response.
|
|
@@ -165,7 +165,7 @@ class SerialConnection:
|
|
|
165
165
|
await self._serial.write(data=encoded_command)
|
|
166
166
|
|
|
167
167
|
async def send_data(
|
|
168
|
-
self, data: str, retries: int = 0, timeout:
|
|
168
|
+
self, data: str, retries: int = 0, timeout: float | None = None
|
|
169
169
|
) -> str:
|
|
170
170
|
"""
|
|
171
171
|
Send data and return the response.
|
|
@@ -184,7 +184,7 @@ class SerialConnection:
|
|
|
184
184
|
):
|
|
185
185
|
return await self._send_data(data=data, retries=retries)
|
|
186
186
|
|
|
187
|
-
async def _send_data(self, data: str, retries: int
|
|
187
|
+
async def _send_data(self, data: str, retries: int) -> str:
|
|
188
188
|
"""
|
|
189
189
|
Send data and return the response.
|
|
190
190
|
|
|
@@ -351,14 +351,14 @@ class AsyncResponseSerialConnection(SerialConnection):
|
|
|
351
351
|
baud_rate: int,
|
|
352
352
|
timeout: float,
|
|
353
353
|
ack: str,
|
|
354
|
-
name:
|
|
354
|
+
name: str | None = None,
|
|
355
355
|
retry_wait_time_seconds: float = 0.1,
|
|
356
|
-
loop:
|
|
357
|
-
error_keyword:
|
|
358
|
-
alarm_keyword:
|
|
356
|
+
loop: asyncio.AbstractEventLoop | None = None,
|
|
357
|
+
error_keyword: str | None = None,
|
|
358
|
+
alarm_keyword: str | None = None,
|
|
359
359
|
reset_buffer_before_write: bool = False,
|
|
360
360
|
error_codes: Type[BaseErrorCode] = DefaultErrorCodes,
|
|
361
|
-
async_error_ack:
|
|
361
|
+
async_error_ack: str | None = None,
|
|
362
362
|
number_of_retries: int = 0,
|
|
363
363
|
) -> AsyncResponseSerialConnection:
|
|
364
364
|
"""
|
|
@@ -461,6 +461,27 @@ class AsyncResponseSerialConnection(SerialConnection):
|
|
|
461
461
|
self._alarm_keyword = alarm_keyword.lower()
|
|
462
462
|
self._async_error_ack = async_error_ack.lower()
|
|
463
463
|
|
|
464
|
+
async def send_multiack_command(
|
|
465
|
+
self,
|
|
466
|
+
command: CommandBuilder,
|
|
467
|
+
retries: int = 0,
|
|
468
|
+
timeout: float | None = None,
|
|
469
|
+
acks: int = 1,
|
|
470
|
+
) -> list[str]:
|
|
471
|
+
"""Send a command and return the responses.
|
|
472
|
+
|
|
473
|
+
Some commands result in multiple responses; collate them and return them all.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
command: A command builder.
|
|
477
|
+
retries: number of times to retry in case of timeout
|
|
478
|
+
timeout: optional override of default timeout in seconds
|
|
479
|
+
acks: the number of acks to expect
|
|
480
|
+
"""
|
|
481
|
+
return await self.send_data_multiack(
|
|
482
|
+
data=command.build(), retries=retries, timeout=timeout, acks=acks
|
|
483
|
+
)
|
|
484
|
+
|
|
464
485
|
async def send_command(
|
|
465
486
|
self,
|
|
466
487
|
command: CommandBuilder,
|
|
@@ -485,6 +506,17 @@ class AsyncResponseSerialConnection(SerialConnection):
|
|
|
485
506
|
timeout=timeout,
|
|
486
507
|
)
|
|
487
508
|
|
|
509
|
+
async def send_data_multiack(
|
|
510
|
+
self, data: str, retries: int = 0, timeout: float | None = None, acks: int = 1
|
|
511
|
+
) -> list[str]:
|
|
512
|
+
"""Send data and return all responses."""
|
|
513
|
+
async with super().send_data_lock, self._serial.timeout_override(
|
|
514
|
+
"timeout", timeout
|
|
515
|
+
):
|
|
516
|
+
return await self._send_data_multiack(
|
|
517
|
+
data=data, retries=retries or self._number_of_retries, acks=acks
|
|
518
|
+
)
|
|
519
|
+
|
|
488
520
|
async def send_data(
|
|
489
521
|
self, data: str, retries: int | None = None, timeout: float | None = None
|
|
490
522
|
) -> str:
|
|
@@ -508,53 +540,98 @@ class AsyncResponseSerialConnection(SerialConnection):
|
|
|
508
540
|
retries=retries if retries is not None else self._number_of_retries,
|
|
509
541
|
)
|
|
510
542
|
|
|
511
|
-
async def
|
|
543
|
+
async def _consume_responses(
|
|
544
|
+
self, acks: int
|
|
545
|
+
) -> AsyncIterator[tuple[Literal["response", "error", "empty-unknown"], bytes]]:
|
|
546
|
+
while acks > 0:
|
|
547
|
+
data = await self._serial.read_until(match=self._ack)
|
|
548
|
+
log.debug(f"{self._name}: Read <- {data!r}")
|
|
549
|
+
if self._async_error_ack.encode() in data:
|
|
550
|
+
yield "error", data
|
|
551
|
+
elif self._ack in data:
|
|
552
|
+
yield "response", data
|
|
553
|
+
acks -= 1
|
|
554
|
+
else:
|
|
555
|
+
# A read timeout, end
|
|
556
|
+
yield "empty-unknown", data
|
|
557
|
+
|
|
558
|
+
async def _send_one_retry(self, data: str, acks: int) -> list[str]:
|
|
559
|
+
data_encode = data.encode("utf-8")
|
|
560
|
+
log.debug(f"{self._name}: Write -> {data_encode!r}")
|
|
561
|
+
await self._serial.write(data=data_encode)
|
|
562
|
+
|
|
563
|
+
command_acks: list[bytes] = []
|
|
564
|
+
async_errors: list[bytes] = []
|
|
565
|
+
# consume responses before raising so we don't raise and orphan
|
|
566
|
+
# a response in the buffer
|
|
567
|
+
async for response_type, response in self._consume_responses(acks):
|
|
568
|
+
if response_type == "error":
|
|
569
|
+
async_errors.append(response)
|
|
570
|
+
elif response_type == "response":
|
|
571
|
+
command_acks.append(response)
|
|
572
|
+
else:
|
|
573
|
+
break
|
|
574
|
+
|
|
575
|
+
for async_error in async_errors:
|
|
576
|
+
# Remove ack from response
|
|
577
|
+
ackless_response = async_error.replace(self._ack, b"")
|
|
578
|
+
str_response = self.process_raw_response(
|
|
579
|
+
command=data, response=ackless_response.decode()
|
|
580
|
+
)
|
|
581
|
+
self.raise_on_error(response=str_response, request=data)
|
|
582
|
+
|
|
583
|
+
ackless_responses: list[str] = []
|
|
584
|
+
for command_ack in command_acks:
|
|
585
|
+
# Remove ack from response
|
|
586
|
+
ackless_response = command_ack.replace(self._ack, b"")
|
|
587
|
+
str_response = self.process_raw_response(
|
|
588
|
+
command=data, response=ackless_response.decode()
|
|
589
|
+
)
|
|
590
|
+
self.raise_on_error(response=str_response, request=data)
|
|
591
|
+
ackless_responses.append(str_response)
|
|
592
|
+
return ackless_responses
|
|
593
|
+
|
|
594
|
+
async def _send_data_multiack(
|
|
595
|
+
self, data: str, retries: int, acks: int
|
|
596
|
+
) -> list[str]:
|
|
512
597
|
"""
|
|
513
|
-
Send data and return the response.
|
|
598
|
+
Send data and return the response(s).
|
|
514
599
|
|
|
515
600
|
Args:
|
|
516
601
|
data: The data to send.
|
|
517
602
|
retries: number of times to retry in case of timeout
|
|
603
|
+
acks: The number of expected command responses
|
|
518
604
|
|
|
519
|
-
|
|
605
|
+
This function retries (resends the command) up to (retries) times, and waits
|
|
606
|
+
for (acks) responses. It also listens for async errors. These are an older
|
|
607
|
+
mechanism where at the moment an error occurs, some modules will send a message
|
|
608
|
+
like async error ERR:202:whatever
|
|
520
609
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
610
|
+
This function will detect async error messages if they were sent before it
|
|
611
|
+
sent the command or if they are sent before the final ack for the command is
|
|
612
|
+
sent. It will not catch async errors otherwise.
|
|
524
613
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
await self._serial.write(data=data_encode)
|
|
614
|
+
This function will always try and consume all the acknowledgements specified for
|
|
615
|
+
its command if it sends the command, even if an async error happens in between.
|
|
528
616
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
while self._async_error_ack.encode() in response[-1].lower():
|
|
534
|
-
# check for multiple a priori async errors
|
|
535
|
-
response.append(await self._serial.read_until(match=self._ack))
|
|
536
|
-
log.debug(f"{self._name}: Read <- {response[-1]!r}")
|
|
537
|
-
|
|
538
|
-
for r in response:
|
|
539
|
-
if self._async_error_ack.encode() in r:
|
|
540
|
-
# Remove ack from response
|
|
541
|
-
ackless_response = r.replace(self._ack, b"")
|
|
542
|
-
str_response = self.process_raw_response(
|
|
543
|
-
command=data, response=ackless_response.decode()
|
|
544
|
-
)
|
|
545
|
-
self.raise_on_error(response=str_response, request=data)
|
|
617
|
+
This should all work together to make sure that there aren't any leftover acks
|
|
618
|
+
after the function ends, which could lead to the read/write mechanics getting out
|
|
619
|
+
of sync.
|
|
546
620
|
|
|
547
|
-
|
|
548
|
-
# Remove ack from response
|
|
549
|
-
ackless_response = response[-1].replace(self._ack, b"")
|
|
550
|
-
str_response = self.process_raw_response(
|
|
551
|
-
command=data, response=ackless_response.decode()
|
|
552
|
-
)
|
|
553
|
-
self.raise_on_error(response=str_response, request=data)
|
|
554
|
-
return str_response
|
|
621
|
+
Returns: The command responses
|
|
555
622
|
|
|
556
|
-
|
|
623
|
+
Raises: SerialException from an error ack to this command or an async error.
|
|
624
|
+
"""
|
|
625
|
+
responses: list[str] = []
|
|
557
626
|
|
|
627
|
+
for retry in range(retries + 1):
|
|
628
|
+
responses = await self._send_one_retry(data, acks)
|
|
629
|
+
if responses:
|
|
630
|
+
return responses
|
|
631
|
+
log.info(f"{self._name}: retry number {retry}/{retries}")
|
|
558
632
|
await self.on_retry()
|
|
559
633
|
|
|
560
634
|
raise NoResponse(port=self._port, command=data)
|
|
635
|
+
|
|
636
|
+
async def _send_data(self, data: str, retries: int) -> str:
|
|
637
|
+
return (await self._send_data_multiack(data, retries, 1))[0]
|
|
@@ -27,6 +27,7 @@ class GCODE(str, Enum):
|
|
|
27
27
|
GET_LABWARE_LATCH_STATE = "M241"
|
|
28
28
|
DEACTIVATE_HEATER = "M106"
|
|
29
29
|
GET_RESET_REASON = "M114"
|
|
30
|
+
GET_ERROR_STATE = "M411"
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
HS_BAUDRATE = 115200
|
|
@@ -202,3 +203,12 @@ class HeaterShakerDriver(AbstractHeaterShakerDriver):
|
|
|
202
203
|
gcode=GCODE.DEACTIVATE_HEATER
|
|
203
204
|
)
|
|
204
205
|
await self._connection.send_command(command=c, retries=DEFAULT_COMMAND_RETRIES)
|
|
206
|
+
|
|
207
|
+
async def get_error_state(self) -> None:
|
|
208
|
+
"""Raise if the module is in an error state."""
|
|
209
|
+
await self._connection.send_multiack_command(
|
|
210
|
+
command=CommandBuilder(terminator=HS_COMMAND_TERMINATOR).add_gcode(
|
|
211
|
+
gcode=GCODE.GET_ERROR_STATE
|
|
212
|
+
),
|
|
213
|
+
acks=2,
|
|
214
|
+
)
|
|
@@ -55,6 +55,7 @@ class AbstractThermocyclerDriver(ABC):
|
|
|
55
55
|
temp: float,
|
|
56
56
|
hold_time: Optional[float] = None,
|
|
57
57
|
volume: Optional[float] = None,
|
|
58
|
+
ramp_rate: Optional[float] = None,
|
|
58
59
|
) -> None:
|
|
59
60
|
"""Send set plate temperature command"""
|
|
60
61
|
...
|
|
@@ -97,3 +98,8 @@ class AbstractThermocyclerDriver(ABC):
|
|
|
97
98
|
async def jog_lid(self, angle: float) -> None:
|
|
98
99
|
"""Send the Jog Lid command."""
|
|
99
100
|
...
|
|
101
|
+
|
|
102
|
+
@abstractmethod
|
|
103
|
+
async def get_error_state(self) -> None:
|
|
104
|
+
"""Raise if the thermocycler is in an error state."""
|
|
105
|
+
...
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
5
|
from enum import Enum
|
|
6
|
-
from typing import Optional, Dict, Union
|
|
6
|
+
from typing import Optional, Dict, Union, TypeVar, Generic
|
|
7
7
|
|
|
8
8
|
from opentrons.drivers import utils
|
|
9
9
|
from opentrons.drivers.command_builder import CommandBuilder
|
|
@@ -36,6 +36,7 @@ class GCODE(str, Enum):
|
|
|
36
36
|
DEVICE_INFO = "M115"
|
|
37
37
|
GET_RESET_REASON = "M114"
|
|
38
38
|
ENTER_PROGRAMMING = "dfu"
|
|
39
|
+
GET_ERROR_STATE = "M411"
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
LID_TARGET_DEFAULT = 105 # Degree celsius (floats)
|
|
@@ -73,7 +74,7 @@ class ThermocyclerDriverFactory:
|
|
|
73
74
|
@staticmethod
|
|
74
75
|
async def create(
|
|
75
76
|
port: str, loop: Optional[asyncio.AbstractEventLoop]
|
|
76
|
-
) -> ThermocyclerDriver:
|
|
77
|
+
) -> ThermocyclerDriver | ThermocyclerDriverV2:
|
|
77
78
|
"""
|
|
78
79
|
Create a thermocycler driver.
|
|
79
80
|
|
|
@@ -148,10 +149,15 @@ class ThermocyclerDriverFactory:
|
|
|
148
149
|
return response.startswith(GCODE.DEVICE_INFO)
|
|
149
150
|
|
|
150
151
|
|
|
151
|
-
|
|
152
|
+
_ConnectionKind = TypeVar(
|
|
153
|
+
"_ConnectionKind", SerialConnection, AsyncResponseSerialConnection
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class _BaseThermocyclerDriver(AbstractThermocyclerDriver, Generic[_ConnectionKind]):
|
|
152
158
|
def __init__(
|
|
153
159
|
self,
|
|
154
|
-
connection:
|
|
160
|
+
connection: _ConnectionKind,
|
|
155
161
|
) -> None:
|
|
156
162
|
"""
|
|
157
163
|
Constructor
|
|
@@ -159,7 +165,7 @@ class ThermocyclerDriver(AbstractThermocyclerDriver):
|
|
|
159
165
|
Args:
|
|
160
166
|
connection: SerialConnection to the thermocycler
|
|
161
167
|
"""
|
|
162
|
-
self._connection = connection
|
|
168
|
+
self._connection: _ConnectionKind = connection
|
|
163
169
|
|
|
164
170
|
async def connect(self) -> None:
|
|
165
171
|
"""Connect to thermocycler"""
|
|
@@ -226,6 +232,7 @@ class ThermocyclerDriver(AbstractThermocyclerDriver):
|
|
|
226
232
|
temp: float,
|
|
227
233
|
hold_time: Optional[float] = None,
|
|
228
234
|
volume: Optional[float] = None,
|
|
235
|
+
ramp_rate: Optional[float] = None,
|
|
229
236
|
) -> None:
|
|
230
237
|
"""Send set plate temperature command"""
|
|
231
238
|
temp = min(BLOCK_TARGET_MAX, max(BLOCK_TARGET_MIN, temp))
|
|
@@ -328,8 +335,15 @@ class ThermocyclerDriver(AbstractThermocyclerDriver):
|
|
|
328
335
|
"Gen1 Thermocyclers do not support the Jog Lid command."
|
|
329
336
|
)
|
|
330
337
|
|
|
338
|
+
async def get_error_state(self) -> None:
|
|
339
|
+
"""For the gen1, do nothing."""
|
|
340
|
+
pass
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
ThermocyclerDriver = _BaseThermocyclerDriver[SerialConnection]
|
|
331
344
|
|
|
332
|
-
|
|
345
|
+
|
|
346
|
+
class ThermocyclerDriverV2(_BaseThermocyclerDriver[AsyncResponseSerialConnection]):
|
|
333
347
|
"""
|
|
334
348
|
This driver is for Thermocycler model Gen2.
|
|
335
349
|
"""
|
|
@@ -343,10 +357,38 @@ class ThermocyclerDriverV2(ThermocyclerDriver):
|
|
|
343
357
|
"""
|
|
344
358
|
super().__init__(connection)
|
|
345
359
|
|
|
346
|
-
async def
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
360
|
+
async def set_plate_temperature(
|
|
361
|
+
self,
|
|
362
|
+
temp: float,
|
|
363
|
+
hold_time: Optional[float] = None,
|
|
364
|
+
volume: Optional[float] = None,
|
|
365
|
+
ramp_rate: Optional[float] = None,
|
|
366
|
+
) -> None:
|
|
367
|
+
"""Send set plate temperature command"""
|
|
368
|
+
temp = min(BLOCK_TARGET_MAX, max(BLOCK_TARGET_MIN, temp))
|
|
369
|
+
|
|
370
|
+
c = (
|
|
371
|
+
CommandBuilder(terminator=TC_COMMAND_TERMINATOR)
|
|
372
|
+
.add_gcode(gcode=GCODE.SET_PLATE_TEMP)
|
|
373
|
+
.add_float(
|
|
374
|
+
prefix="S", value=temp, precision=utils.TC_GCODE_ROUNDING_PRECISION
|
|
375
|
+
)
|
|
376
|
+
)
|
|
377
|
+
if hold_time is not None:
|
|
378
|
+
c = c.add_float(
|
|
379
|
+
prefix="H", value=hold_time, precision=utils.TC_GCODE_ROUNDING_PRECISION
|
|
380
|
+
)
|
|
381
|
+
if volume is not None:
|
|
382
|
+
c = c.add_float(
|
|
383
|
+
prefix="V", value=volume, precision=utils.TC_GCODE_ROUNDING_PRECISION
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
if ramp_rate is not None:
|
|
387
|
+
c = c.add_float(
|
|
388
|
+
prefix="R", value=ramp_rate, precision=utils.TC_GCODE_ROUNDING_PRECISION
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
await self._connection.send_command(command=c, retries=DEFAULT_COMMAND_RETRIES)
|
|
350
392
|
|
|
351
393
|
async def get_device_info(self) -> Dict[str, str]:
|
|
352
394
|
"""Send get device info command"""
|
|
@@ -393,3 +435,12 @@ class ThermocyclerDriverV2(ThermocyclerDriver):
|
|
|
393
435
|
.add_element("O")
|
|
394
436
|
)
|
|
395
437
|
await self._connection.send_command(command=c, retries=1)
|
|
438
|
+
|
|
439
|
+
async def get_error_state(self) -> None:
|
|
440
|
+
"""Raise an error if the thermocycler is stuck in an error state."""
|
|
441
|
+
await self._connection.send_multiack_command(
|
|
442
|
+
command=CommandBuilder(terminator=TC_COMMAND_TERMINATOR).add_gcode(
|
|
443
|
+
gcode=GCODE.GET_ERROR_STATE
|
|
444
|
+
),
|
|
445
|
+
acks=2,
|
|
446
|
+
)
|
|
@@ -67,10 +67,12 @@ class SimulatingDriver(AbstractThermocyclerDriver):
|
|
|
67
67
|
temp: float,
|
|
68
68
|
hold_time: Optional[float] = None,
|
|
69
69
|
volume: Optional[float] = None,
|
|
70
|
+
ramp_rate: Optional[float] = None,
|
|
70
71
|
) -> None:
|
|
71
72
|
self._plate_temperature.target = temp
|
|
72
73
|
self._plate_temperature.current = temp
|
|
73
74
|
self._plate_temperature.hold = 0
|
|
75
|
+
self._ramp_rate = ramp_rate
|
|
74
76
|
|
|
75
77
|
@ensure_yield
|
|
76
78
|
async def get_plate_temperature(self) -> PlateTemperature:
|
|
@@ -124,3 +126,7 @@ class SimulatingDriver(AbstractThermocyclerDriver):
|
|
|
124
126
|
if angle < 0
|
|
125
127
|
else ThermocyclerLidStatus.OPEN
|
|
126
128
|
)
|
|
129
|
+
|
|
130
|
+
@ensure_yield
|
|
131
|
+
async def get_error_state(self) -> None:
|
|
132
|
+
return
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from typing import Protocol, Optional
|
|
2
|
+
|
|
3
|
+
from .types import VacuumModuleInfo, LEDColor, LEDPattern
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AbstractVacuumModuleDriver(Protocol):
|
|
7
|
+
"""Protocol for the Vacuum Module driver."""
|
|
8
|
+
|
|
9
|
+
async def connect(self) -> None:
|
|
10
|
+
"""Connect to vacuum module."""
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
async def disconnect(self) -> None:
|
|
14
|
+
"""Disconnect from vacuum module."""
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
async def is_connected(self) -> bool:
|
|
18
|
+
"""Check connection to vacuum module."""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
async def get_device_info(self) -> VacuumModuleInfo:
|
|
22
|
+
"""Get Device Info."""
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
async def set_serial_number(self, sn: str) -> None:
|
|
26
|
+
"""Set Serial Number."""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
async def enable_pump(self) -> None:
|
|
30
|
+
"""Enable the vacuum pump."""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
async def disable_pump(self) -> None:
|
|
34
|
+
"""Disable the vacuum pump."""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
async def get_pump_motor_register(self) -> None:
|
|
38
|
+
"""Get the register value of the pump motor driver."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
async def get_pressure_sensor_register(self) -> None:
|
|
42
|
+
"""Get the register value of the pressure sensor driver."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
async def get_pressure_sensor_reading_psi(self) -> float:
|
|
46
|
+
"""Get a reading from the pressure sensor."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
async def set_vacuum_chamber_pressure(
|
|
50
|
+
self,
|
|
51
|
+
gage_pressure_mbarg: float,
|
|
52
|
+
duration: Optional[float],
|
|
53
|
+
rate: Optional[float],
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Engage or release the vacuum until a desired internal pressure is reached."""
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
async def get_gage_pressure_reading_mbarg(self) -> float:
|
|
59
|
+
"""Read each pressure sensor and return the pressure difference."""
|
|
60
|
+
return 0.0
|
|
61
|
+
|
|
62
|
+
# TODO: change pump power to be more specific when we find out how were gonna operate that
|
|
63
|
+
async def engage_vacuum(self, pump_power: Optional[float] = None) -> None:
|
|
64
|
+
"""Engage the vacuum without regard to chamber pressure."""
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
async def disengage_vacuum_pump(self) -> None:
|
|
68
|
+
"""Stops the vacuum pump, doesn't vent air or disable the motor."""
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
async def vent(self) -> None:
|
|
72
|
+
"""Release the vacuum in the module chamber."""
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
async def set_led(
|
|
76
|
+
self,
|
|
77
|
+
power: float,
|
|
78
|
+
color: Optional[LEDColor] = None,
|
|
79
|
+
external: Optional[bool] = None,
|
|
80
|
+
pattern: Optional[LEDPattern] = None,
|
|
81
|
+
duration: Optional[int] = None, # Default firmware duration is 500ms
|
|
82
|
+
reps: Optional[int] = None, # Default firmware reps is 0
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Set LED Status bar color and pattern."""
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
async def enter_programming_mode(self) -> None:
|
|
88
|
+
"""Reboot into programming mode"""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
async def reset_serial_buffers(self) -> None:
|
|
92
|
+
"""Reset the input and output serial buffers."""
|
|
93
|
+
...
|