opentrons 8.4.0a4__py2.py3-none-any.whl → 8.4.0a5__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/protocol_api/core/engine/instrument.py +30 -34
- opentrons/protocol_api/core/engine/transfer_components_executor.py +18 -2
- opentrons/protocol_api/core/instrument.py +4 -3
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +5 -3
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +4 -3
- opentrons/protocol_api/instrument_context.py +30 -15
- opentrons/protocol_api/labware.py +19 -38
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +38 -59
- opentrons/protocol_engine/commands/dispense_while_tracking.py +36 -67
- opentrons/protocol_engine/execution/pipetting.py +1 -0
- opentrons/util/logging_config.py +94 -25
- opentrons/util/logging_queue_handler.py +61 -0
- {opentrons-8.4.0a4.dist-info → opentrons-8.4.0a5.dist-info}/METADATA +4 -4
- {opentrons-8.4.0a4.dist-info → opentrons-8.4.0a5.dist-info}/RECORD +18 -17
- {opentrons-8.4.0a4.dist-info → opentrons-8.4.0a5.dist-info}/LICENSE +0 -0
- {opentrons-8.4.0a4.dist-info → opentrons-8.4.0a5.dist-info}/WHEEL +0 -0
- {opentrons-8.4.0a4.dist-info → opentrons-8.4.0a5.dist-info}/entry_points.txt +0 -0
- {opentrons-8.4.0a4.dist-info → opentrons-8.4.0a5.dist-info}/top_level.txt +0 -0
|
@@ -12,6 +12,7 @@ from typing import (
|
|
|
12
12
|
Tuple,
|
|
13
13
|
NamedTuple,
|
|
14
14
|
Generator,
|
|
15
|
+
Literal,
|
|
15
16
|
)
|
|
16
17
|
from opentrons.types import (
|
|
17
18
|
Location,
|
|
@@ -747,6 +748,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
747
748
|
force_direct: bool,
|
|
748
749
|
minimum_z_height: Optional[float],
|
|
749
750
|
speed: Optional[float],
|
|
751
|
+
check_for_movement_conflicts: bool = True,
|
|
750
752
|
) -> None:
|
|
751
753
|
if well_core is not None:
|
|
752
754
|
if isinstance(location, (TrashBin, WasteChute)):
|
|
@@ -765,6 +767,14 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
765
767
|
assert isinstance(well_location, LiquidHandlingWellLocation)
|
|
766
768
|
if well_location.volumeOffset and well_location.volumeOffset != 0:
|
|
767
769
|
raise ValueError("volume offset not supported with move_to")
|
|
770
|
+
if check_for_movement_conflicts:
|
|
771
|
+
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
772
|
+
engine_state=self._engine_client.state,
|
|
773
|
+
pipette_id=self._pipette_id,
|
|
774
|
+
labware_id=labware_id,
|
|
775
|
+
well_name=well_name,
|
|
776
|
+
well_location=well_location,
|
|
777
|
+
)
|
|
768
778
|
self._engine_client.execute_command(
|
|
769
779
|
cmd.MoveToWellParams(
|
|
770
780
|
pipetteId=self._pipette_id,
|
|
@@ -1399,7 +1409,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1399
1409
|
volume: float,
|
|
1400
1410
|
source: Tuple[Location, WellCore],
|
|
1401
1411
|
dest: List[Tuple[Location, WellCore]],
|
|
1402
|
-
new_tip: TransferTipPolicyV2,
|
|
1412
|
+
new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
|
|
1403
1413
|
tip_racks: List[Tuple[Location, LabwareCore]],
|
|
1404
1414
|
starting_tip: Optional[WellCore],
|
|
1405
1415
|
trash_location: Union[Location, TrashBin, WasteChute],
|
|
@@ -1415,8 +1425,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1415
1425
|
dest: List of destination wells, with each well represented as a tuple of
|
|
1416
1426
|
types.Location and WellCore.
|
|
1417
1427
|
types.Location is only necessary for saving the last accessed location.
|
|
1418
|
-
new_tip: Whether the transfer should use a new tip 'once'
|
|
1419
|
-
or 'per source'.
|
|
1428
|
+
new_tip: Whether the transfer should use a new tip 'once' or 'never'.
|
|
1420
1429
|
tiprack_uri: The URI of the tiprack that the transfer settings are for.
|
|
1421
1430
|
tip_drop_location: Location where the tip will be dropped (if appropriate).
|
|
1422
1431
|
|
|
@@ -1432,6 +1441,8 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1432
1441
|
raise RuntimeError(
|
|
1433
1442
|
"No tipracks found for pipette in order to perform transfer"
|
|
1434
1443
|
)
|
|
1444
|
+
assert new_tip in [TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE]
|
|
1445
|
+
|
|
1435
1446
|
tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
|
|
1436
1447
|
working_volume = min(
|
|
1437
1448
|
self.get_max_volume(),
|
|
@@ -1559,7 +1570,6 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1559
1570
|
)
|
|
1560
1571
|
return tip_well
|
|
1561
1572
|
|
|
1562
|
-
tip_used = False
|
|
1563
1573
|
if new_tip != TransferTipPolicyV2.NEVER:
|
|
1564
1574
|
last_tip_picked_up_from = _pick_up_tip()
|
|
1565
1575
|
|
|
@@ -1604,16 +1614,6 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1604
1614
|
)
|
|
1605
1615
|
)
|
|
1606
1616
|
|
|
1607
|
-
if new_tip == TransferTipPolicyV2.ALWAYS and tip_used:
|
|
1608
|
-
_drop_tip()
|
|
1609
|
-
last_tip_picked_up_from = _pick_up_tip()
|
|
1610
|
-
tip_contents = [
|
|
1611
|
-
tx_comps_executor.LiquidAndAirGapPair(
|
|
1612
|
-
liquid=0,
|
|
1613
|
-
air_gap=0,
|
|
1614
|
-
)
|
|
1615
|
-
]
|
|
1616
|
-
|
|
1617
1617
|
use_single_dispense = False
|
|
1618
1618
|
if total_aspirate_volume == volume and len(vol_dest_combo) == 1:
|
|
1619
1619
|
# We are only doing a single transfer. Either because this is the last
|
|
@@ -1696,7 +1696,6 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1696
1696
|
disposal_volume=disposal_vol,
|
|
1697
1697
|
)
|
|
1698
1698
|
is_first_step = False
|
|
1699
|
-
tip_used = True
|
|
1700
1699
|
|
|
1701
1700
|
if new_tip != TransferTipPolicyV2.NEVER:
|
|
1702
1701
|
_drop_tip()
|
|
@@ -1728,7 +1727,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1728
1727
|
volume: float,
|
|
1729
1728
|
source: List[Tuple[Location, WellCore]],
|
|
1730
1729
|
dest: Tuple[Location, WellCore],
|
|
1731
|
-
new_tip: TransferTipPolicyV2,
|
|
1730
|
+
new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
|
|
1732
1731
|
tip_racks: List[Tuple[Location, LabwareCore]],
|
|
1733
1732
|
starting_tip: Optional[WellCore],
|
|
1734
1733
|
trash_location: Union[Location, TrashBin, WasteChute],
|
|
@@ -1738,7 +1737,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1738
1737
|
raise RuntimeError(
|
|
1739
1738
|
"No tipracks found for pipette in order to perform transfer"
|
|
1740
1739
|
)
|
|
1741
|
-
|
|
1740
|
+
assert new_tip in [TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE]
|
|
1742
1741
|
tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
|
|
1743
1742
|
try:
|
|
1744
1743
|
transfer_props = liquid_class.get_for(
|
|
@@ -1841,7 +1840,6 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1841
1840
|
if new_tip == TransferTipPolicyV2.ONCE:
|
|
1842
1841
|
last_tip_picked_up_from = _pick_up_tip()
|
|
1843
1842
|
|
|
1844
|
-
prev_src: Optional[Tuple[Location, WellCore]] = None
|
|
1845
1843
|
tip_contents = [
|
|
1846
1844
|
tx_comps_executor.LiquidAndAirGapPair(
|
|
1847
1845
|
liquid=0,
|
|
@@ -1849,6 +1847,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1849
1847
|
)
|
|
1850
1848
|
]
|
|
1851
1849
|
next_step_volume, next_source = next(source_per_volume_step)
|
|
1850
|
+
is_first_step = True
|
|
1852
1851
|
is_last_step = False
|
|
1853
1852
|
while not is_last_step:
|
|
1854
1853
|
total_dispense_volume = 0.0
|
|
@@ -1863,21 +1862,17 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1863
1862
|
is_last_step = True
|
|
1864
1863
|
break
|
|
1865
1864
|
|
|
1866
|
-
if
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
with self.lpd_for_transfer(enable=False):
|
|
1878
|
-
for step_num, (step_volume, step_source) in enumerate(
|
|
1879
|
-
vol_aspirate_combo
|
|
1880
|
-
):
|
|
1865
|
+
if (
|
|
1866
|
+
self.get_liquid_presence_detection()
|
|
1867
|
+
and new_tip != TransferTipPolicyV2.NEVER
|
|
1868
|
+
and is_first_step
|
|
1869
|
+
):
|
|
1870
|
+
enable_lpd = True
|
|
1871
|
+
else:
|
|
1872
|
+
enable_lpd = False
|
|
1873
|
+
|
|
1874
|
+
for step_num, (step_volume, step_source) in enumerate(vol_aspirate_combo):
|
|
1875
|
+
with self.lpd_for_transfer(enable=enable_lpd):
|
|
1881
1876
|
tip_contents = self.aspirate_liquid_class(
|
|
1882
1877
|
volume=step_volume,
|
|
1883
1878
|
source=step_source,
|
|
@@ -1888,6 +1883,8 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1888
1883
|
total_dispense_volume if step_num == 0 else None
|
|
1889
1884
|
),
|
|
1890
1885
|
)
|
|
1886
|
+
is_first_step = False
|
|
1887
|
+
enable_lpd = False
|
|
1891
1888
|
tip_contents = self.dispense_liquid_class(
|
|
1892
1889
|
volume=total_dispense_volume,
|
|
1893
1890
|
dest=dest,
|
|
@@ -1902,7 +1899,6 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1902
1899
|
),
|
|
1903
1900
|
trash_location=trash_location,
|
|
1904
1901
|
)
|
|
1905
|
-
prev_src = next_source
|
|
1906
1902
|
if new_tip != TransferTipPolicyV2.NEVER:
|
|
1907
1903
|
_drop_tip()
|
|
1908
1904
|
|
|
@@ -99,6 +99,14 @@ class TipState:
|
|
|
99
99
|
), "Last air gap volume doe not match the volume being removed"
|
|
100
100
|
self.last_liquid_and_air_gap_in_tip.air_gap = 0
|
|
101
101
|
|
|
102
|
+
def delete_last_air_gap_and_liquid(self) -> None:
|
|
103
|
+
air_gap_in_tip = self.last_liquid_and_air_gap_in_tip.air_gap
|
|
104
|
+
liquid_in_tip = self.last_liquid_and_air_gap_in_tip.liquid
|
|
105
|
+
if air_gap_in_tip:
|
|
106
|
+
self.delete_air_gap(air_gap_in_tip)
|
|
107
|
+
if liquid_in_tip:
|
|
108
|
+
self.delete_liquid(volume=liquid_in_tip)
|
|
109
|
+
|
|
102
110
|
|
|
103
111
|
class TransferType(Enum):
|
|
104
112
|
ONE_TO_ONE = "one_to_one"
|
|
@@ -525,6 +533,9 @@ class TransferComponentsExecutor:
|
|
|
525
533
|
if isinstance(trash_location, Location)
|
|
526
534
|
else None
|
|
527
535
|
)
|
|
536
|
+
# A non-multi-dispense blowout will only have air and maybe droplets in the tip
|
|
537
|
+
# since we only blowout after dispensing the full tip contents.
|
|
538
|
+
# So delete the air gap from tip state
|
|
528
539
|
last_air_gap = self._tip_state.last_liquid_and_air_gap_in_tip.air_gap
|
|
529
540
|
self._tip_state.delete_air_gap(last_air_gap)
|
|
530
541
|
self._tip_state.ready_to_aspirate = False
|
|
@@ -608,6 +619,10 @@ class TransferComponentsExecutor:
|
|
|
608
619
|
well_core=None,
|
|
609
620
|
in_place=True,
|
|
610
621
|
)
|
|
622
|
+
# A blowout will remove all air gap and liquid (disposal volume) from the tip
|
|
623
|
+
# so delete them from tip state (although practically, there will not be
|
|
624
|
+
# any air gaps in the tip before blowing out in the destination well)
|
|
625
|
+
self._tip_state.delete_last_air_gap_and_liquid()
|
|
611
626
|
self._tip_state.ready_to_aspirate = False
|
|
612
627
|
|
|
613
628
|
# A retract will perform total of two air gaps if we need to blow out in source or trash:
|
|
@@ -695,8 +710,9 @@ class TransferComponentsExecutor:
|
|
|
695
710
|
if isinstance(trash_location, Location)
|
|
696
711
|
else None
|
|
697
712
|
)
|
|
698
|
-
|
|
699
|
-
|
|
713
|
+
# A blowout will remove all air gap and liquid (disposal volume) from the tip
|
|
714
|
+
# so delete them from tip state
|
|
715
|
+
self._tip_state.delete_last_air_gap_and_liquid()
|
|
700
716
|
self._tip_state.ready_to_aspirate = False
|
|
701
717
|
|
|
702
718
|
# Do touch tip and air gap again after blowing out into source well or trash
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from abc import abstractmethod, ABC
|
|
6
|
-
from typing import Any, Generic, Optional, TypeVar, Union, List, Tuple
|
|
6
|
+
from typing import Any, Generic, Optional, TypeVar, Union, List, Tuple, Literal
|
|
7
7
|
|
|
8
8
|
from opentrons import types
|
|
9
9
|
from opentrons.hardware_control.dev_types import PipetteDict
|
|
@@ -190,6 +190,7 @@ class AbstractInstrument(ABC, Generic[WellCoreType, LabwareCoreType]):
|
|
|
190
190
|
force_direct: bool,
|
|
191
191
|
minimum_z_height: Optional[float],
|
|
192
192
|
speed: Optional[float],
|
|
193
|
+
check_for_movement_conflicts: bool,
|
|
193
194
|
) -> None:
|
|
194
195
|
...
|
|
195
196
|
|
|
@@ -381,7 +382,7 @@ class AbstractInstrument(ABC, Generic[WellCoreType, LabwareCoreType]):
|
|
|
381
382
|
volume: float,
|
|
382
383
|
source: Tuple[types.Location, WellCoreType],
|
|
383
384
|
dest: List[Tuple[types.Location, WellCoreType]],
|
|
384
|
-
new_tip: TransferTipPolicyV2,
|
|
385
|
+
new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
|
|
385
386
|
tip_racks: List[Tuple[types.Location, LabwareCoreType]],
|
|
386
387
|
starting_tip: Optional[WellCoreType],
|
|
387
388
|
trash_location: Union[types.Location, TrashBin, WasteChute],
|
|
@@ -400,7 +401,7 @@ class AbstractInstrument(ABC, Generic[WellCoreType, LabwareCoreType]):
|
|
|
400
401
|
volume: float,
|
|
401
402
|
source: List[Tuple[types.Location, WellCoreType]],
|
|
402
403
|
dest: Tuple[types.Location, WellCoreType],
|
|
403
|
-
new_tip: TransferTipPolicyV2,
|
|
404
|
+
new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
|
|
404
405
|
tip_racks: List[Tuple[types.Location, LabwareCoreType]],
|
|
405
406
|
starting_tip: Optional[WellCoreType],
|
|
406
407
|
trash_location: Union[types.Location, TrashBin, WasteChute],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import TYPE_CHECKING, Optional, Union, List, Tuple
|
|
4
|
+
from typing import TYPE_CHECKING, Optional, Union, List, Tuple, Literal
|
|
5
5
|
|
|
6
6
|
from opentrons import types
|
|
7
7
|
from opentrons.hardware_control import CriticalPoint
|
|
@@ -367,6 +367,7 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore, LegacyLabwareCore]
|
|
|
367
367
|
force_direct: bool = False,
|
|
368
368
|
minimum_z_height: Optional[float] = None,
|
|
369
369
|
speed: Optional[float] = None,
|
|
370
|
+
check_for_movement_conflicts: bool = False,
|
|
370
371
|
) -> None:
|
|
371
372
|
"""Move the instrument.
|
|
372
373
|
|
|
@@ -376,6 +377,7 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore, LegacyLabwareCore]
|
|
|
376
377
|
force_direct: Force a direct movement instead of an arc.
|
|
377
378
|
minimum_z_height: Set a minimum travel height for a movement arc.
|
|
378
379
|
speed: Override the travel speed in mm/s.
|
|
380
|
+
check_for_movement_conflicts: Not used in legacy implementation
|
|
379
381
|
|
|
380
382
|
Raises:
|
|
381
383
|
LabwareHeightError: An item on the deck is taller than
|
|
@@ -619,7 +621,7 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore, LegacyLabwareCore]
|
|
|
619
621
|
volume: float,
|
|
620
622
|
source: Tuple[types.Location, LegacyWellCore],
|
|
621
623
|
dest: List[Tuple[types.Location, LegacyWellCore]],
|
|
622
|
-
new_tip: TransferTipPolicyV2,
|
|
624
|
+
new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
|
|
623
625
|
tip_racks: List[Tuple[types.Location, LegacyLabwareCore]],
|
|
624
626
|
starting_tip: Optional[LegacyWellCore],
|
|
625
627
|
trash_location: Union[types.Location, TrashBin, WasteChute],
|
|
@@ -634,7 +636,7 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore, LegacyLabwareCore]
|
|
|
634
636
|
volume: float,
|
|
635
637
|
source: List[Tuple[types.Location, LegacyWellCore]],
|
|
636
638
|
dest: Tuple[types.Location, LegacyWellCore],
|
|
637
|
-
new_tip: TransferTipPolicyV2,
|
|
639
|
+
new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
|
|
638
640
|
tip_racks: List[Tuple[types.Location, LegacyLabwareCore]],
|
|
639
641
|
starting_tip: Optional[LegacyWellCore],
|
|
640
642
|
trash_location: Union[types.Location, TrashBin, WasteChute],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import TYPE_CHECKING, Optional, Union, List, Tuple
|
|
4
|
+
from typing import TYPE_CHECKING, Optional, Union, List, Tuple, Literal
|
|
5
5
|
|
|
6
6
|
from opentrons import types
|
|
7
7
|
from opentrons.hardware_control.dev_types import PipetteDict
|
|
@@ -325,6 +325,7 @@ class LegacyInstrumentCoreSimulator(
|
|
|
325
325
|
force_direct: bool = False,
|
|
326
326
|
minimum_z_height: Optional[float] = None,
|
|
327
327
|
speed: Optional[float] = None,
|
|
328
|
+
check_for_movement_conflicts: bool = False, # Not used in this implementation
|
|
328
329
|
) -> None:
|
|
329
330
|
"""Simulation of only the motion planning portion of move_to."""
|
|
330
331
|
if isinstance(location, (TrashBin, WasteChute)):
|
|
@@ -534,7 +535,7 @@ class LegacyInstrumentCoreSimulator(
|
|
|
534
535
|
volume: float,
|
|
535
536
|
source: Tuple[types.Location, LegacyWellCore],
|
|
536
537
|
dest: List[Tuple[types.Location, LegacyWellCore]],
|
|
537
|
-
new_tip: TransferTipPolicyV2,
|
|
538
|
+
new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
|
|
538
539
|
tip_racks: List[Tuple[types.Location, LegacyLabwareCore]],
|
|
539
540
|
starting_tip: Optional[LegacyWellCore],
|
|
540
541
|
trash_location: Union[types.Location, TrashBin, WasteChute],
|
|
@@ -549,7 +550,7 @@ class LegacyInstrumentCoreSimulator(
|
|
|
549
550
|
volume: float,
|
|
550
551
|
source: List[Tuple[types.Location, LegacyWellCore]],
|
|
551
552
|
dest: Tuple[types.Location, LegacyWellCore],
|
|
552
|
-
new_tip: TransferTipPolicyV2,
|
|
553
|
+
new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
|
|
553
554
|
tip_racks: List[Tuple[types.Location, LegacyLabwareCore]],
|
|
554
555
|
starting_tip: Optional[LegacyWellCore],
|
|
555
556
|
trash_location: Union[types.Location, TrashBin, WasteChute],
|
|
@@ -1529,7 +1529,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1529
1529
|
"""Move a particular type of liquid from one well or group of wells to another.
|
|
1530
1530
|
|
|
1531
1531
|
:param liquid_class: The type of liquid to move. You must specify the liquid class,
|
|
1532
|
-
even if you have used :py:meth:`.load_liquid` to indicate what liquid the
|
|
1532
|
+
even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
|
|
1533
1533
|
source contains.
|
|
1534
1534
|
:type liquid_class: :py:class:`.LiquidClass`
|
|
1535
1535
|
|
|
@@ -1552,6 +1552,8 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1552
1552
|
tips. Depending on the liquid class, the pipette may also blow out liquid here.
|
|
1553
1553
|
:param return_tip: Whether to drop used tips in their original locations
|
|
1554
1554
|
in the tip rack, instead of the trash.
|
|
1555
|
+
|
|
1556
|
+
:meta private:
|
|
1555
1557
|
"""
|
|
1556
1558
|
if volume == 0.0:
|
|
1557
1559
|
_log.info(
|
|
@@ -1634,7 +1636,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1634
1636
|
Distribute a particular type of liquid from one well to a group of wells.
|
|
1635
1637
|
|
|
1636
1638
|
:param liquid_class: The type of liquid to move. You must specify the liquid class,
|
|
1637
|
-
even if you have used :py:meth:`.load_liquid` to indicate what liquid the
|
|
1639
|
+
even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
|
|
1638
1640
|
source contains.
|
|
1639
1641
|
:type liquid_class: :py:class:`.LiquidClass`
|
|
1640
1642
|
|
|
@@ -1645,8 +1647,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1645
1647
|
:param new_tip: When to pick up and drop tips during the command.
|
|
1646
1648
|
Defaults to ``"once"``.
|
|
1647
1649
|
|
|
1648
|
-
- ``"once"
|
|
1649
|
-
- ``"always"``: Use a new tip for each set of aspirate and dispense steps.
|
|
1650
|
+
- ``"once"``: Use one tip for the entire command.
|
|
1650
1651
|
- ``"never"``: Do not pick up or drop tips at all.
|
|
1651
1652
|
|
|
1652
1653
|
See :ref:`param-tip-handling` for details.
|
|
@@ -1655,6 +1656,8 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1655
1656
|
tips. Depending on the liquid class, the pipette may also blow out liquid here.
|
|
1656
1657
|
:param return_tip: Whether to drop used tips in their original locations
|
|
1657
1658
|
in the tip rack, instead of the trash.
|
|
1659
|
+
|
|
1660
|
+
:meta private:
|
|
1658
1661
|
"""
|
|
1659
1662
|
if volume == 0.0:
|
|
1660
1663
|
_log.info(
|
|
@@ -1681,9 +1684,14 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1681
1684
|
f"Source should be a single well (or resolve to a single transfer for multi-channel) "
|
|
1682
1685
|
f"but received {transfer_args.sources_list}."
|
|
1683
1686
|
)
|
|
1684
|
-
if transfer_args.tip_policy
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
+
if transfer_args.tip_policy not in [
|
|
1688
|
+
TransferTipPolicyV2.ONCE,
|
|
1689
|
+
TransferTipPolicyV2.NEVER,
|
|
1690
|
+
]:
|
|
1691
|
+
raise ValueError(
|
|
1692
|
+
f"Incompatible `new_tip` value of {new_tip}."
|
|
1693
|
+
f" `distribute_with_liquid_class()` only supports `new_tip` values of"
|
|
1694
|
+
f" 'once' and 'never'."
|
|
1687
1695
|
)
|
|
1688
1696
|
|
|
1689
1697
|
verified_source = transfer_args.sources_list[0]
|
|
@@ -1708,7 +1716,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1708
1716
|
(types.Location(types.Point(), labware=well), well._core)
|
|
1709
1717
|
for well in transfer_args.destinations_list
|
|
1710
1718
|
],
|
|
1711
|
-
new_tip=transfer_args.tip_policy,
|
|
1719
|
+
new_tip=transfer_args.tip_policy, # type: ignore[arg-type]
|
|
1712
1720
|
tip_racks=[
|
|
1713
1721
|
(types.Location(types.Point(), labware=rack), rack._core)
|
|
1714
1722
|
for rack in transfer_args.tip_racks
|
|
@@ -1741,7 +1749,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1741
1749
|
Consolidate a particular type of liquid from a group of wells to one well.
|
|
1742
1750
|
|
|
1743
1751
|
:param liquid_class: The type of liquid to move. You must specify the liquid class,
|
|
1744
|
-
even if you have used :py:meth:`.load_liquid` to indicate what liquid the
|
|
1752
|
+
even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
|
|
1745
1753
|
source contains.
|
|
1746
1754
|
:type liquid_class: :py:class:`.LiquidClass`
|
|
1747
1755
|
|
|
@@ -1753,8 +1761,6 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1753
1761
|
Defaults to ``"once"``.
|
|
1754
1762
|
|
|
1755
1763
|
- ``"once"``: Use one tip for the entire command.
|
|
1756
|
-
- ``"always"``: Use a new tip for each set of aspirate and dispense steps.
|
|
1757
|
-
- ``"per source"``: Not available when consolidating.
|
|
1758
1764
|
- ``"never"``: Do not pick up or drop tips at all.
|
|
1759
1765
|
|
|
1760
1766
|
See :ref:`param-tip-handling` for details.
|
|
@@ -1763,6 +1769,8 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1763
1769
|
tips. Depending on the liquid class, the pipette may also blow out liquid here.
|
|
1764
1770
|
:param return_tip: Whether to drop used tips in their original locations
|
|
1765
1771
|
in the tip rack, instead of the trash.
|
|
1772
|
+
|
|
1773
|
+
:meta private:
|
|
1766
1774
|
"""
|
|
1767
1775
|
if volume == 0.0:
|
|
1768
1776
|
_log.info(
|
|
@@ -1789,9 +1797,14 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1789
1797
|
f"Destination should be a single well (or resolve to a single transfer for multi-channel) "
|
|
1790
1798
|
f"but received {transfer_args.destinations_list}."
|
|
1791
1799
|
)
|
|
1792
|
-
if transfer_args.tip_policy
|
|
1793
|
-
|
|
1794
|
-
|
|
1800
|
+
if transfer_args.tip_policy not in [
|
|
1801
|
+
TransferTipPolicyV2.ONCE,
|
|
1802
|
+
TransferTipPolicyV2.NEVER,
|
|
1803
|
+
]:
|
|
1804
|
+
raise ValueError(
|
|
1805
|
+
f"Incompatible `new_tip` value of {new_tip}."
|
|
1806
|
+
f" `consolidate_with_liquid_class()` only supports `new_tip` values of"
|
|
1807
|
+
f" 'once' and 'never'."
|
|
1795
1808
|
)
|
|
1796
1809
|
|
|
1797
1810
|
verified_dest = transfer_args.destinations_list[0]
|
|
@@ -1816,7 +1829,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1816
1829
|
types.Location(types.Point(), labware=verified_dest),
|
|
1817
1830
|
verified_dest._core,
|
|
1818
1831
|
),
|
|
1819
|
-
new_tip=transfer_args.tip_policy,
|
|
1832
|
+
new_tip=transfer_args.tip_policy, # type: ignore[arg-type]
|
|
1820
1833
|
tip_racks=[
|
|
1821
1834
|
(types.Location(types.Point(), labware=rack), rack._core)
|
|
1822
1835
|
for rack in transfer_args.tip_racks
|
|
@@ -1907,6 +1920,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1907
1920
|
force_direct=force_direct,
|
|
1908
1921
|
minimum_z_height=minimum_z_height,
|
|
1909
1922
|
speed=speed,
|
|
1923
|
+
check_for_movement_conflicts=False,
|
|
1910
1924
|
)
|
|
1911
1925
|
else:
|
|
1912
1926
|
if publish:
|
|
@@ -1925,6 +1939,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1925
1939
|
force_direct=force_direct,
|
|
1926
1940
|
minimum_z_height=minimum_z_height,
|
|
1927
1941
|
speed=speed,
|
|
1942
|
+
check_for_movement_conflicts=False,
|
|
1928
1943
|
)
|
|
1929
1944
|
|
|
1930
1945
|
return self
|
|
@@ -263,15 +263,15 @@ class Well:
|
|
|
263
263
|
|
|
264
264
|
@requires_version(2, 21)
|
|
265
265
|
def meniscus(
|
|
266
|
-
self, target: Literal["start", "end", "dynamic"]
|
|
266
|
+
self, z: float = 0.0, target: Literal["start", "end", "dynamic"] = "end"
|
|
267
267
|
) -> Location:
|
|
268
268
|
"""
|
|
269
269
|
:param z: An offset on the z-axis, in mm. Positive offsets are higher and
|
|
270
270
|
negative offsets are lower.
|
|
271
|
-
:param target: The relative position inside the well to target when performing a liquid handling operation.
|
|
272
|
-
|
|
271
|
+
:param target: The relative position of the liquid meniscus inside the well to target when performing a liquid handling operation.
|
|
272
|
+
|
|
273
|
+
:return: A :py:class:`~opentrons.types.Location` corresponding to the liquid meniscus, plus a target position and ``z`` offset as specified.
|
|
273
274
|
|
|
274
|
-
:meta private:
|
|
275
275
|
"""
|
|
276
276
|
return Location(
|
|
277
277
|
point=Point(x=0, y=0, z=z),
|
|
@@ -326,9 +326,9 @@ class Well:
|
|
|
326
326
|
:param Liquid liquid: The liquid to load into the well.
|
|
327
327
|
:param float volume: The volume of liquid to load, in µL.
|
|
328
328
|
|
|
329
|
-
..
|
|
330
|
-
|
|
331
|
-
|
|
329
|
+
.. deprecated:: 2.22
|
|
330
|
+
Use :py:meth:`.Labware.load_liquid`, :py:meth:`.Labware.load_liquid_by_well`, or :py:meth:`.Labware.load_empty` instead.
|
|
331
|
+
|
|
332
332
|
"""
|
|
333
333
|
self._core.load_liquid(
|
|
334
334
|
liquid=liquid,
|
|
@@ -1270,22 +1270,15 @@ class Labware:
|
|
|
1270
1270
|
) -> None:
|
|
1271
1271
|
"""Mark several wells as containing the same amount of liquid.
|
|
1272
1272
|
|
|
1273
|
-
This method should be called at the beginning of a protocol, soon after loading
|
|
1274
|
-
liquid handling operations begin.
|
|
1275
|
-
|
|
1276
|
-
:py:meth:`~Labware.load_liquid_by_well`, the volume it contains is unknown and the well's liquid will not be tracked.
|
|
1277
|
-
|
|
1278
|
-
For example, to load 10µL of a liquid named ``water`` (defined with :py:meth:`~ProtocolContext.define_liquid`)
|
|
1279
|
-
into all the wells of a labware, you could call ``labware.load_liquid(labware.wells(), 10, water)``.
|
|
1280
|
-
|
|
1281
|
-
If you want to load different volumes of liquid into different wells, use :py:meth:`~Labware.load_liquid_by_well`.
|
|
1282
|
-
|
|
1283
|
-
If you want to mark the well as containing no liquid, use :py:meth:`~Labware.load_empty`.
|
|
1273
|
+
This method should be called at the beginning of a protocol, soon after loading labware and before
|
|
1274
|
+
liquid handling operations begin. Loading liquids is required for liquid tracking functionality. If a well
|
|
1275
|
+
hasn't been assigned a starting volume with :py:meth:`~Labware.load_empty`, :py:meth:`~Labware.load_liquid`, or
|
|
1276
|
+
:py:meth:`~Labware.load_liquid_by_well`, the volume it contains is unknown and the well's liquid will not be tracked throughout the protocol.
|
|
1284
1277
|
|
|
1285
1278
|
:param wells: The wells to load the liquid into.
|
|
1286
|
-
:type wells: List of well names or list of Well objects
|
|
1279
|
+
:type wells: List of string well names or list of :py:class:`.Well` objects (e.g., from :py:meth:`~Labware.wells`).
|
|
1287
1280
|
|
|
1288
|
-
:param volume: The volume of liquid to load into each well
|
|
1281
|
+
:param volume: The volume of liquid to load into each well.
|
|
1289
1282
|
:type volume: float
|
|
1290
1283
|
|
|
1291
1284
|
:param liquid: The liquid to load into each well, previously defined by :py:meth:`~ProtocolContext.define_liquid`
|
|
@@ -1321,18 +1314,9 @@ class Labware:
|
|
|
1321
1314
|
) -> None:
|
|
1322
1315
|
"""Mark several wells as containing unique volumes of liquid.
|
|
1323
1316
|
|
|
1324
|
-
This method should be called at the beginning of a protocol, soon after loading
|
|
1325
|
-
liquid handling
|
|
1326
|
-
|
|
1327
|
-
:py:meth:`~Labware.load_liquid_by_well`, the volume it contains is unknown and the well's liquid will not be tracked.
|
|
1328
|
-
|
|
1329
|
-
For example, to load a decreasing amount of of a liquid named ``water`` (defined with :py:meth:`~ProtocolContext.define_liquid`)
|
|
1330
|
-
into each successive well of a row, you could call
|
|
1331
|
-
``labware.load_liquid_by_well({'A1': 1000, 'A2': 950, 'A3': 900, ..., 'A12': 600}, water)``
|
|
1332
|
-
|
|
1333
|
-
If you want to load the same volume of a liquid into multiple wells, it is often easier to use :py:meth:`~Labware.load_liquid`.
|
|
1334
|
-
|
|
1335
|
-
If you want to mark the well as containing no liquid, use :py:meth:`~Labware.load_empty`.
|
|
1317
|
+
This method should be called at the beginning of a protocol, soon after loading labware and before
|
|
1318
|
+
liquid handling begins. Loading liquids is required for liquid tracking functionality. If a well hasn't been assigned a starting volume with :py:meth:`~Labware.load_empty`, :py:meth:`~Labware.load_liquid`, or
|
|
1319
|
+
:py:meth:`~Labware.load_liquid_by_well`, the volume it contains is unknown and the well's liquid will not be tracked throughout the protocol.
|
|
1336
1320
|
|
|
1337
1321
|
:param volumes: A dictionary of well names (or :py:class:`Well` objects, for instance from ``labware['A1']``)
|
|
1338
1322
|
:type wells: Dict[Union[str, Well], float]
|
|
@@ -1368,12 +1352,9 @@ class Labware:
|
|
|
1368
1352
|
def load_empty(self, wells: Sequence[Union[Well, str]]) -> None:
|
|
1369
1353
|
"""Mark several wells as empty.
|
|
1370
1354
|
|
|
1371
|
-
This method should be called at the beginning of a protocol,
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
volume it contains is unknown and the well's liquid will not be tracked.
|
|
1375
|
-
|
|
1376
|
-
For instance, to mark all wells in the labware as empty, you can call ``labware.load_empty(labware.wells())``.
|
|
1355
|
+
This method should be called at the beginning of a protocol, after loading the labware and before liquid handling
|
|
1356
|
+
begins. Loading liquids is required for liquid tracking functionality. If a well in a labware hasn't been assigned a starting volume with :py:meth:`Labware.load_empty`, :py:meth:`Labware.load_liquid`, or :py:meth:`Labware.load_liquid_by_well`, the
|
|
1357
|
+
volume it contains is unknown and the well's liquid will not be tracked throughout the protocol.
|
|
1377
1358
|
|
|
1378
1359
|
:param wells: The list of wells to mark empty. To mark all wells as empty, pass ``labware.wells()``. You can also specify
|
|
1379
1360
|
wells by their names (for instance, ``labware.load_empty(['A1', 'A2'])``).
|
|
@@ -29,7 +29,7 @@ from ..state.update_types import StateUpdate
|
|
|
29
29
|
from ..errors.exceptions import PipetteNotReadyToAspirateError
|
|
30
30
|
from opentrons.hardware_control import HardwareControlAPI
|
|
31
31
|
from ..state.update_types import CLEAR
|
|
32
|
-
from ..types import
|
|
32
|
+
from ..types import DeckPoint
|
|
33
33
|
|
|
34
34
|
if TYPE_CHECKING:
|
|
35
35
|
from ..execution import PipettingHandler, GantryMover, MovementHandler
|
|
@@ -104,11 +104,8 @@ class AspirateWhileTrackingImplementation(
|
|
|
104
104
|
" The first aspirate following a blow-out must be from a specific well"
|
|
105
105
|
" so the plunger can be reset in a known safe position."
|
|
106
106
|
)
|
|
107
|
-
|
|
108
|
-
current_position = await self._gantry_mover.get_position(params.pipetteId)
|
|
109
|
-
current_location = self._state_view.pipettes.get_current_location()
|
|
110
|
-
|
|
111
107
|
state_update = StateUpdate()
|
|
108
|
+
|
|
112
109
|
move_result = await move_to_well(
|
|
113
110
|
movement=self._movement,
|
|
114
111
|
model_utils=self._model_utils,
|
|
@@ -132,9 +129,9 @@ class AspirateWhileTrackingImplementation(
|
|
|
132
129
|
flow_rate=params.flowRate,
|
|
133
130
|
location_if_error={
|
|
134
131
|
"retryLocation": (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
move_result.public.position.x,
|
|
133
|
+
move_result.public.position.y,
|
|
134
|
+
move_result.public.position.z,
|
|
138
135
|
)
|
|
139
136
|
},
|
|
140
137
|
command_note_adder=self._command_note_adder,
|
|
@@ -150,58 +147,40 @@ class AspirateWhileTrackingImplementation(
|
|
|
150
147
|
z=position_after_aspirate.z,
|
|
151
148
|
)
|
|
152
149
|
if isinstance(aspirate_result, DefinedErrorData):
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
|
|
162
|
-
current_location.labware_id,
|
|
163
|
-
current_location.well_name,
|
|
164
|
-
params.pipetteId,
|
|
165
|
-
),
|
|
166
|
-
volume_added=CLEAR,
|
|
167
|
-
),
|
|
168
|
-
state_update_if_false_positive=aspirate_result.state_update_if_false_positive,
|
|
169
|
-
)
|
|
170
|
-
else:
|
|
171
|
-
return aspirate_result
|
|
172
|
-
else:
|
|
173
|
-
if (
|
|
174
|
-
isinstance(current_location, CurrentWell)
|
|
175
|
-
and current_location.pipette_id == params.pipetteId
|
|
176
|
-
):
|
|
177
|
-
return SuccessData(
|
|
178
|
-
public=AspirateWhileTrackingResult(
|
|
179
|
-
volume=aspirate_result.public.volume,
|
|
180
|
-
position=result_deck_point,
|
|
181
|
-
),
|
|
182
|
-
state_update=aspirate_result.state_update.set_liquid_operated(
|
|
183
|
-
labware_id=current_location.labware_id,
|
|
184
|
-
well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
|
|
185
|
-
current_location.labware_id,
|
|
186
|
-
current_location.well_name,
|
|
187
|
-
params.pipetteId,
|
|
188
|
-
),
|
|
189
|
-
volume_added=-aspirate_result.public.volume
|
|
190
|
-
* self._state_view.geometry.get_nozzles_per_well(
|
|
191
|
-
current_location.labware_id,
|
|
192
|
-
current_location.well_name,
|
|
193
|
-
params.pipetteId,
|
|
194
|
-
),
|
|
195
|
-
),
|
|
196
|
-
)
|
|
197
|
-
else:
|
|
198
|
-
return SuccessData(
|
|
199
|
-
public=AspirateWhileTrackingResult(
|
|
200
|
-
volume=aspirate_result.public.volume,
|
|
201
|
-
position=result_deck_point,
|
|
150
|
+
return DefinedErrorData(
|
|
151
|
+
public=aspirate_result.public,
|
|
152
|
+
state_update=aspirate_result.state_update.set_liquid_operated(
|
|
153
|
+
labware_id=params.labwareId,
|
|
154
|
+
well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
|
|
155
|
+
params.labwareId,
|
|
156
|
+
params.wellName,
|
|
157
|
+
params.pipetteId,
|
|
202
158
|
),
|
|
203
|
-
|
|
204
|
-
)
|
|
159
|
+
volume_added=CLEAR,
|
|
160
|
+
),
|
|
161
|
+
state_update_if_false_positive=aspirate_result.state_update_if_false_positive,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return SuccessData(
|
|
165
|
+
public=AspirateWhileTrackingResult(
|
|
166
|
+
volume=aspirate_result.public.volume,
|
|
167
|
+
position=result_deck_point,
|
|
168
|
+
),
|
|
169
|
+
state_update=aspirate_result.state_update.set_liquid_operated(
|
|
170
|
+
labware_id=params.labwareId,
|
|
171
|
+
well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
|
|
172
|
+
params.labwareId,
|
|
173
|
+
params.wellName,
|
|
174
|
+
params.pipetteId,
|
|
175
|
+
),
|
|
176
|
+
volume_added=-aspirate_result.public.volume
|
|
177
|
+
* self._state_view.geometry.get_nozzles_per_well(
|
|
178
|
+
params.labwareId,
|
|
179
|
+
params.wellName,
|
|
180
|
+
params.pipetteId,
|
|
181
|
+
),
|
|
182
|
+
),
|
|
183
|
+
)
|
|
205
184
|
|
|
206
185
|
|
|
207
186
|
class AspirateWhileTracking(
|
|
@@ -9,7 +9,7 @@ from pydantic import Field
|
|
|
9
9
|
from pydantic.json_schema import SkipJsonSchema
|
|
10
10
|
|
|
11
11
|
from ..state.update_types import CLEAR, StateUpdate
|
|
12
|
-
from ..types import
|
|
12
|
+
from ..types import DeckPoint
|
|
13
13
|
from .pipetting_common import (
|
|
14
14
|
PipetteIdMixin,
|
|
15
15
|
DispenseVolumeMixin,
|
|
@@ -99,9 +99,6 @@ class DispenseWhileTrackingImplementation(
|
|
|
99
99
|
|
|
100
100
|
# TODO(pbm, 10-15-24): call self._state_view.geometry.validate_dispense_volume_into_well()
|
|
101
101
|
|
|
102
|
-
current_location = self._state_view.pipettes.get_current_location()
|
|
103
|
-
current_position = await self._gantry_mover.get_position(params.pipetteId)
|
|
104
|
-
|
|
105
102
|
state_update = StateUpdate()
|
|
106
103
|
move_result = await move_to_well(
|
|
107
104
|
movement=self._movement,
|
|
@@ -110,7 +107,6 @@ class DispenseWhileTrackingImplementation(
|
|
|
110
107
|
labware_id=params.labwareId,
|
|
111
108
|
well_name=params.wellName,
|
|
112
109
|
well_location=params.wellLocation,
|
|
113
|
-
operation_volume=-params.volume,
|
|
114
110
|
)
|
|
115
111
|
state_update.append(move_result.state_update)
|
|
116
112
|
if isinstance(move_result, DefinedErrorData):
|
|
@@ -127,9 +123,9 @@ class DispenseWhileTrackingImplementation(
|
|
|
127
123
|
push_out=params.pushOut,
|
|
128
124
|
location_if_error={
|
|
129
125
|
"retryLocation": (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
126
|
+
move_result.public.position.x,
|
|
127
|
+
move_result.public.position.y,
|
|
128
|
+
move_result.public.position.z,
|
|
133
129
|
)
|
|
134
130
|
},
|
|
135
131
|
pipetting=self._pipetting,
|
|
@@ -145,67 +141,40 @@ class DispenseWhileTrackingImplementation(
|
|
|
145
141
|
)
|
|
146
142
|
|
|
147
143
|
if isinstance(dispense_result, DefinedErrorData):
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
labware_id=current_location.labware_id,
|
|
156
|
-
well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
|
|
157
|
-
current_location.labware_id,
|
|
158
|
-
current_location.well_name,
|
|
159
|
-
params.pipetteId,
|
|
160
|
-
),
|
|
161
|
-
volume_added=CLEAR,
|
|
162
|
-
),
|
|
163
|
-
state_update_if_false_positive=dispense_result.state_update_if_false_positive,
|
|
164
|
-
)
|
|
165
|
-
else:
|
|
166
|
-
return dispense_result
|
|
167
|
-
else:
|
|
168
|
-
if (
|
|
169
|
-
isinstance(current_location, CurrentWell)
|
|
170
|
-
and current_location.pipette_id == params.pipetteId
|
|
171
|
-
):
|
|
172
|
-
volume_added = (
|
|
173
|
-
self._state_view.pipettes.get_liquid_dispensed_by_ejecting_volume(
|
|
174
|
-
pipette_id=params.pipetteId,
|
|
175
|
-
volume=dispense_result.public.volume,
|
|
176
|
-
)
|
|
177
|
-
)
|
|
178
|
-
if volume_added is not None:
|
|
179
|
-
volume_added *= self._state_view.geometry.get_nozzles_per_well(
|
|
180
|
-
current_location.labware_id,
|
|
181
|
-
current_location.well_name,
|
|
144
|
+
return DefinedErrorData(
|
|
145
|
+
public=dispense_result.public,
|
|
146
|
+
state_update=dispense_result.state_update.set_liquid_operated(
|
|
147
|
+
labware_id=params.labwareId,
|
|
148
|
+
well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
|
|
149
|
+
params.labwareId,
|
|
150
|
+
params.wellName,
|
|
182
151
|
params.pipetteId,
|
|
183
|
-
)
|
|
184
|
-
return SuccessData(
|
|
185
|
-
public=DispenseWhileTrackingResult(
|
|
186
|
-
volume=dispense_result.public.volume,
|
|
187
|
-
position=result_deck_point,
|
|
188
|
-
),
|
|
189
|
-
state_update=dispense_result.state_update.set_liquid_operated(
|
|
190
|
-
labware_id=current_location.labware_id,
|
|
191
|
-
well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
|
|
192
|
-
current_location.labware_id,
|
|
193
|
-
current_location.well_name,
|
|
194
|
-
params.pipetteId,
|
|
195
|
-
),
|
|
196
|
-
volume_added=volume_added
|
|
197
|
-
if volume_added is not None
|
|
198
|
-
else CLEAR,
|
|
199
152
|
),
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
153
|
+
volume_added=CLEAR,
|
|
154
|
+
),
|
|
155
|
+
state_update_if_false_positive=dispense_result.state_update_if_false_positive,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return SuccessData(
|
|
159
|
+
public=DispenseWhileTrackingResult(
|
|
160
|
+
volume=dispense_result.public.volume,
|
|
161
|
+
position=result_deck_point,
|
|
162
|
+
),
|
|
163
|
+
state_update=dispense_result.state_update.set_liquid_operated(
|
|
164
|
+
labware_id=params.labwareId,
|
|
165
|
+
well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
|
|
166
|
+
params.labwareId,
|
|
167
|
+
params.wellName,
|
|
168
|
+
params.pipetteId,
|
|
169
|
+
),
|
|
170
|
+
volume_added=dispense_result.public.volume
|
|
171
|
+
* self._state_view.geometry.get_nozzles_per_well(
|
|
172
|
+
params.labwareId,
|
|
173
|
+
params.wellName,
|
|
174
|
+
params.pipetteId,
|
|
175
|
+
),
|
|
176
|
+
),
|
|
177
|
+
)
|
|
209
178
|
|
|
210
179
|
|
|
211
180
|
class DispenseWhileTracking(
|
opentrons/util/logging_config.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from logging.config import dictConfig
|
|
3
|
+
from logging.handlers import QueueListener, RotatingFileHandler
|
|
3
4
|
import sys
|
|
4
|
-
from
|
|
5
|
+
from queue import Queue
|
|
5
6
|
|
|
6
7
|
from opentrons.config import CONFIG, ARCHITECTURE, SystemArchitecture
|
|
7
8
|
|
|
@@ -12,11 +13,33 @@ else:
|
|
|
12
13
|
SENSOR_LOG_NAME = "unused"
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
# We want this big enough to smooth over any temporary stalls in journald's ability
|
|
17
|
+
# to consume our records--but bounded, so if we consistently outpace journald for
|
|
18
|
+
# some reason, we don't leak memory or get latency from buffer bloat.
|
|
19
|
+
# 50000 is basically an arbitrary guess.
|
|
20
|
+
_LOG_QUEUE_SIZE = 50000
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
log_queue = Queue[logging.LogRecord](maxsize=_LOG_QUEUE_SIZE)
|
|
24
|
+
"""A buffer through which log records will pass.
|
|
25
|
+
|
|
26
|
+
This is intended to work around problems when our logs are going to journald:
|
|
27
|
+
we think journald can block for a while when it flushes records to the filesystem,
|
|
28
|
+
and the backpressure from that will cause calls like `log.debug()` to block and
|
|
29
|
+
interfere with timing-sensitive hardware control.
|
|
30
|
+
https://github.com/Opentrons/opentrons/issues/18034
|
|
31
|
+
|
|
32
|
+
`log_init()` will configure all the logs that this package knows about to pass through
|
|
33
|
+
this queue. This queue is exposed so consumers of this package (i.e. robot-server)
|
|
34
|
+
can do the same thing with their own logs, which is important to preserve ordering.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _config_for_host(level_value: int) -> None:
|
|
16
39
|
serial_log_filename = CONFIG["serial_log_file"]
|
|
17
40
|
api_log_filename = CONFIG["api_log_file"]
|
|
18
41
|
sensor_log_filename = CONFIG["sensor_log_file"]
|
|
19
|
-
|
|
42
|
+
config = {
|
|
20
43
|
"version": 1,
|
|
21
44
|
"disable_existing_loggers": False,
|
|
22
45
|
"formatters": {
|
|
@@ -90,13 +113,20 @@ def _host_config(level_value: int) -> Dict[str, Any]:
|
|
|
90
113
|
},
|
|
91
114
|
}
|
|
92
115
|
|
|
116
|
+
dictConfig(config)
|
|
93
117
|
|
|
94
|
-
|
|
118
|
+
|
|
119
|
+
def _config_for_robot(level_value: int) -> None:
|
|
95
120
|
# Import systemd.journald here since it is generally unavailble on non
|
|
96
121
|
# linux systems and we probably don't want to use it on linux desktops
|
|
97
122
|
# either
|
|
123
|
+
from systemd.journal import JournalHandler # type: ignore
|
|
124
|
+
|
|
98
125
|
sensor_log_filename = CONFIG["sensor_log_file"]
|
|
99
|
-
|
|
126
|
+
|
|
127
|
+
sensor_log_queue = Queue[logging.LogRecord](maxsize=_LOG_QUEUE_SIZE)
|
|
128
|
+
|
|
129
|
+
config = {
|
|
100
130
|
"version": 1,
|
|
101
131
|
"disable_existing_loggers": False,
|
|
102
132
|
"formatters": {
|
|
@@ -104,36 +134,38 @@ def _buildroot_config(level_value: int) -> Dict[str, Any]:
|
|
|
104
134
|
},
|
|
105
135
|
"handlers": {
|
|
106
136
|
"api": {
|
|
107
|
-
"class": "
|
|
137
|
+
"class": "opentrons.util.logging_queue_handler.CustomQueueHandler",
|
|
108
138
|
"level": logging.DEBUG,
|
|
109
139
|
"formatter": "message_only",
|
|
110
|
-
"SYSLOG_IDENTIFIER": "opentrons-api",
|
|
140
|
+
"extra": {"SYSLOG_IDENTIFIER": "opentrons-api"},
|
|
141
|
+
"queue": log_queue,
|
|
111
142
|
},
|
|
112
143
|
"serial": {
|
|
113
|
-
"class": "
|
|
144
|
+
"class": "opentrons.util.logging_queue_handler.CustomQueueHandler",
|
|
114
145
|
"level": logging.DEBUG,
|
|
115
146
|
"formatter": "message_only",
|
|
116
|
-
"SYSLOG_IDENTIFIER": "opentrons-api-serial",
|
|
147
|
+
"extra": {"SYSLOG_IDENTIFIER": "opentrons-api-serial"},
|
|
148
|
+
"queue": log_queue,
|
|
117
149
|
},
|
|
118
150
|
"can_serial": {
|
|
119
|
-
"class": "
|
|
151
|
+
"class": "opentrons.util.logging_queue_handler.CustomQueueHandler",
|
|
120
152
|
"level": logging.DEBUG,
|
|
121
153
|
"formatter": "message_only",
|
|
122
|
-
"SYSLOG_IDENTIFIER": "opentrons-api-serial-can",
|
|
154
|
+
"extra": {"SYSLOG_IDENTIFIER": "opentrons-api-serial-can"},
|
|
155
|
+
"queue": log_queue,
|
|
123
156
|
},
|
|
124
157
|
"usbbin_serial": {
|
|
125
|
-
"class": "
|
|
158
|
+
"class": "opentrons.util.logging_queue_handler.CustomQueueHandler",
|
|
126
159
|
"level": logging.DEBUG,
|
|
127
160
|
"formatter": "message_only",
|
|
128
|
-
"SYSLOG_IDENTIFIER": "opentrons-api-serial-usbbin",
|
|
161
|
+
"extra": {"SYSLOG_IDENTIFIER": "opentrons-api-serial-usbbin"},
|
|
162
|
+
"queue": log_queue,
|
|
129
163
|
},
|
|
130
164
|
"sensor": {
|
|
131
|
-
"class": "
|
|
132
|
-
"formatter": "message_only",
|
|
133
|
-
"filename": sensor_log_filename,
|
|
134
|
-
"maxBytes": 1000000,
|
|
165
|
+
"class": "opentrons.util.logging_queue_handler.CustomQueueHandler",
|
|
135
166
|
"level": logging.DEBUG,
|
|
136
|
-
"
|
|
167
|
+
"formatter": "message_only",
|
|
168
|
+
"queue": sensor_log_queue,
|
|
137
169
|
},
|
|
138
170
|
},
|
|
139
171
|
"loggers": {
|
|
@@ -169,12 +201,47 @@ def _buildroot_config(level_value: int) -> Dict[str, Any]:
|
|
|
169
201
|
},
|
|
170
202
|
}
|
|
171
203
|
|
|
204
|
+
# Start draining from the queue and sending messages to journald.
|
|
205
|
+
# Then, stash the queue listener in a global variable so it doesn't get garbage-collected.
|
|
206
|
+
# I don't know if we actually need to do this, but let's not find out the hard way.
|
|
207
|
+
global _queue_listener
|
|
208
|
+
if _queue_listener is not None:
|
|
209
|
+
# In case this log init function was called multiple times for some reason.
|
|
210
|
+
_queue_listener.stop()
|
|
211
|
+
_queue_listener = QueueListener(log_queue, JournalHandler())
|
|
212
|
+
_queue_listener.start()
|
|
213
|
+
|
|
214
|
+
# Sensor logs are a special one-off thing that go to their own file instead of journald.
|
|
215
|
+
# We apply the same QueueListener performance workaround for basically the same reasons.
|
|
216
|
+
sensor_rotating_file_handler = RotatingFileHandler(
|
|
217
|
+
filename=sensor_log_filename, maxBytes=1000000, backupCount=3
|
|
218
|
+
)
|
|
219
|
+
sensor_rotating_file_handler.setLevel(logging.DEBUG)
|
|
220
|
+
sensor_rotating_file_handler.setFormatter(logging.Formatter(fmt="%(message)s"))
|
|
221
|
+
global _sensor_queue_listener
|
|
222
|
+
if _sensor_queue_listener is not None:
|
|
223
|
+
_sensor_queue_listener.stop()
|
|
224
|
+
_sensor_queue_listener = QueueListener(
|
|
225
|
+
sensor_log_queue, sensor_rotating_file_handler
|
|
226
|
+
)
|
|
227
|
+
_sensor_queue_listener.start()
|
|
228
|
+
|
|
229
|
+
dictConfig(config)
|
|
172
230
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
231
|
+
# TODO(2025-04-15): We need some kind of log_deinit() function to call
|
|
232
|
+
# queue_listener.stop() before the process ends. Not doing that means we're
|
|
233
|
+
# dropping some records when the process shuts down.
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
_queue_listener: QueueListener | None = None
|
|
237
|
+
_sensor_queue_listener: QueueListener | None = None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _config(arch: SystemArchitecture, level_value: int) -> None:
|
|
241
|
+
{
|
|
242
|
+
SystemArchitecture.YOCTO: _config_for_robot,
|
|
243
|
+
SystemArchitecture.BUILDROOT: _config_for_robot,
|
|
244
|
+
SystemArchitecture.HOST: _config_for_host,
|
|
178
245
|
}[arch](level_value)
|
|
179
246
|
|
|
180
247
|
|
|
@@ -191,6 +258,8 @@ def log_init(level_name: str) -> None:
|
|
|
191
258
|
f"Defaulting to {fallback_log_level}\n"
|
|
192
259
|
)
|
|
193
260
|
ot_log_level = fallback_log_level
|
|
261
|
+
|
|
262
|
+
# todo(mm, 2025-04-14): Use logging.getLevelNamesMapping() when we have Python >=3.11.
|
|
194
263
|
level_value = logging._nameToLevel[ot_log_level]
|
|
195
|
-
|
|
196
|
-
|
|
264
|
+
|
|
265
|
+
_config(ARCHITECTURE, level_value)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# noqa: D100
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import logging.handlers
|
|
5
|
+
import logging
|
|
6
|
+
from queue import Queue
|
|
7
|
+
from typing import cast
|
|
8
|
+
from typing_extensions import override
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CustomQueueHandler(logging.handlers.QueueHandler):
|
|
12
|
+
"""A logging.QueueHandler with some customizations.
|
|
13
|
+
|
|
14
|
+
- Allow adding `extra` data to handled log records.
|
|
15
|
+
|
|
16
|
+
- Simplify and optimize for single-process use.
|
|
17
|
+
|
|
18
|
+
- If a new message comes in but the queue is full, block until it has room.
|
|
19
|
+
(The default QueueHandler drops records in a way we probably wouldn't notice.)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self, *, queue: Queue[logging.LogRecord], extra: dict[str, object] | None = None
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Construct the handler.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
queue: When this handler receives a log record, it will insert the message
|
|
29
|
+
into this queue.
|
|
30
|
+
extra: Extra data to attach to each log record, to be interpreted by
|
|
31
|
+
whatever handler is on the consuming side of the queue. e.g. if that's
|
|
32
|
+
`systemd.journal.JournalHandler`, you could add a "SYSLOG_IDENTIFIER"
|
|
33
|
+
key here. This corresponds to the `extra` arg of `Logger.debug()`.
|
|
34
|
+
"""
|
|
35
|
+
super().__init__(queue=queue)
|
|
36
|
+
|
|
37
|
+
# Double underscore because we're subclassing external code so we should try to
|
|
38
|
+
# avoid collisions with its attributes.
|
|
39
|
+
self.__extra = extra
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
def prepare(self, record: logging.LogRecord) -> logging.LogRecord:
|
|
43
|
+
"""Called internally by the superclass before enqueueing a record."""
|
|
44
|
+
if self.__extra is not None:
|
|
45
|
+
# This looks questionable, but updating __dict__ is the documented behavior
|
|
46
|
+
# of `Logger.debug(msg, extra=...)`.
|
|
47
|
+
record.__dict__.update(self.__extra)
|
|
48
|
+
|
|
49
|
+
# We intentionally do *not* call `super().prepare(record)`. It's documented to
|
|
50
|
+
# muck with the data in the LogRecord, apparently as part of supporting
|
|
51
|
+
# inter-process use. Since we don't need that, we can preserve the original
|
|
52
|
+
# data and also save some compute time.
|
|
53
|
+
return record
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
def enqueue(self, record: logging.LogRecord) -> None:
|
|
57
|
+
"""Called internally by the superclass to enqueue a record."""
|
|
58
|
+
# This cast is safe because we constrain the type of `self.queue`
|
|
59
|
+
# in our `__init__()` and nobody should mutate it after-the-fact, in practice.
|
|
60
|
+
queue = cast(Queue[logging.LogRecord], self.queue)
|
|
61
|
+
queue.put(record)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: opentrons
|
|
3
|
-
Version: 8.4.
|
|
3
|
+
Version: 8.4.0a5
|
|
4
4
|
Summary: The Opentrons API is a simple framework designed to make writing automated biology lab protocols easy.
|
|
5
5
|
Author: Opentrons
|
|
6
6
|
Author-email: engineering@opentrons.com
|
|
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
21
21
|
Classifier: Topic :: Scientific/Engineering
|
|
22
22
|
Requires-Python: >=3.10
|
|
23
23
|
License-File: ../LICENSE
|
|
24
|
-
Requires-Dist: opentrons-shared-data (==8.4.
|
|
24
|
+
Requires-Dist: opentrons-shared-data (==8.4.0a5)
|
|
25
25
|
Requires-Dist: aionotify (==0.3.1)
|
|
26
26
|
Requires-Dist: anyio (<4.0.0,>=3.6.1)
|
|
27
27
|
Requires-Dist: jsonschema (<4.18.0,>=3.0.1)
|
|
@@ -35,9 +35,9 @@ Requires-Dist: pyusb (==1.2.1)
|
|
|
35
35
|
Requires-Dist: packaging (>=21.0)
|
|
36
36
|
Requires-Dist: importlib-metadata (>=1.0) ; python_version < "3.8"
|
|
37
37
|
Provides-Extra: flex-hardware
|
|
38
|
-
Requires-Dist: opentrons-hardware[flex] (==8.4.
|
|
38
|
+
Requires-Dist: opentrons-hardware[flex] (==8.4.0a5) ; extra == 'flex-hardware'
|
|
39
39
|
Provides-Extra: ot2-hardware
|
|
40
|
-
Requires-Dist: opentrons-hardware (==8.4.
|
|
40
|
+
Requires-Dist: opentrons-hardware (==8.4.0a5) ; extra == 'ot2-hardware'
|
|
41
41
|
|
|
42
42
|
.. _Full API Documentation: http://docs.opentrons.com
|
|
43
43
|
|
|
@@ -228,8 +228,8 @@ opentrons/protocol_api/config.py,sha256=r9lyvXjagTX_g3q5FGURPpcz2IA9sSF7Oa_1mKx-
|
|
|
228
228
|
opentrons/protocol_api/create_protocol_context.py,sha256=wwsZje0L__oDnu1Yrihau320_f-ASloR9eL1QCtkOh8,7612
|
|
229
229
|
opentrons/protocol_api/deck.py,sha256=94vFceg1SC1bAGd7TvC1ZpYwnJR-VlzurEZ6jkacYeg,8910
|
|
230
230
|
opentrons/protocol_api/disposal_locations.py,sha256=NRiSGmDR0LnbyEkWSOM-o64uR2fUoB1NWJG7Y7SsJSs,7920
|
|
231
|
-
opentrons/protocol_api/instrument_context.py,sha256=
|
|
232
|
-
opentrons/protocol_api/labware.py,sha256=
|
|
231
|
+
opentrons/protocol_api/instrument_context.py,sha256=XUVIxnylpkhviFy5jxYJv-o7Lvmb2FJIbdoEjQ_9Vro,117886
|
|
232
|
+
opentrons/protocol_api/labware.py,sha256=1m1y7h70bBBqW60LjiCPQWwFfVXzY_goJrB773yUN0A,60407
|
|
233
233
|
opentrons/protocol_api/module_contexts.py,sha256=3tVXj6Q7n-WuTJPU_dvIQLzzGv1P-jsMuDtMVpuhAf8,48291
|
|
234
234
|
opentrons/protocol_api/module_validation_and_errors.py,sha256=XL_m72P8rcvGO2fynY7UzXLcpGuI6X4s0V6Xf735Iyc,1464
|
|
235
235
|
opentrons/protocol_api/protocol_context.py,sha256=CHMG5xbx_wxHDQYcobOWo2E9vsRw7FPSNy9J3YV99fM,66126
|
|
@@ -238,7 +238,7 @@ opentrons/protocol_api/validation.py,sha256=uiVTHyJF3wSh5LLfaIDBTELoMNCAT17E767u
|
|
|
238
238
|
opentrons/protocol_api/core/__init__.py,sha256=-g74o8OtBB0LmmOvwkRvPgrHt7fF7T8FRHDj-x_-Onk,736
|
|
239
239
|
opentrons/protocol_api/core/common.py,sha256=q9ZbfpRdBvB3iDAOCyONtupvkYP5n1hjE-bwqGcwP_U,1172
|
|
240
240
|
opentrons/protocol_api/core/core_map.py,sha256=gq3CIYPxuPvozf8yj8FprqBfs3e4ZJGQ6s0ViPbwV08,1757
|
|
241
|
-
opentrons/protocol_api/core/instrument.py,sha256=
|
|
241
|
+
opentrons/protocol_api/core/instrument.py,sha256=MQHI1_MkrtrKM9lN-7BsVQ-xNc4on39TRr8M3HB3WPY,13705
|
|
242
242
|
opentrons/protocol_api/core/labware.py,sha256=-ZOjkalikXCV3ptehKCNaWGAdKxIdwne8LRFQW9NAm4,4290
|
|
243
243
|
opentrons/protocol_api/core/module.py,sha256=z2STDyqqxZX3y6UyJVDnajeFXMEn1ie2NRBYHhry_XE,13838
|
|
244
244
|
opentrons/protocol_api/core/protocol.py,sha256=v7v28jfeHSfOf-tqFDW2chGtrEatPiZ1y6YNwHfmtAs,9058
|
|
@@ -248,7 +248,7 @@ opentrons/protocol_api/core/well_grid.py,sha256=BU28DKaBgEU_JdZ6pEzrwNxmuh6TkO4z
|
|
|
248
248
|
opentrons/protocol_api/core/engine/__init__.py,sha256=B_5T7zgkWDb1mXPg4NbT-wBkQaK-WVokMMnJRNu7xiM,582
|
|
249
249
|
opentrons/protocol_api/core/engine/deck_conflict.py,sha256=q3JViIAHDthIqq6ce7h2gxw3CHRfYsm5kkwzuXB-Gnc,12334
|
|
250
250
|
opentrons/protocol_api/core/engine/exceptions.py,sha256=aZgNrmYEeuPZm21nX_KZYtvyjv5h_zPjxxgPkEV7_bw,725
|
|
251
|
-
opentrons/protocol_api/core/engine/instrument.py,sha256=
|
|
251
|
+
opentrons/protocol_api/core/engine/instrument.py,sha256=qkoNv0KO_jQZffQDV4ixU2r-CkLIJl5IOz1dgs1G5V8,95869
|
|
252
252
|
opentrons/protocol_api/core/engine/labware.py,sha256=1xvzguNnK7aecFLiJK0gtRrZ5kpwtzLS73HnKvdJ5lc,8413
|
|
253
253
|
opentrons/protocol_api/core/engine/load_labware_params.py,sha256=I4Cb8rqpBhmykQuZE8QRG802APrdCy_TYS88rm_9oGA,7159
|
|
254
254
|
opentrons/protocol_api/core/engine/module_core.py,sha256=MLPgYSRJHUZPZ9rTLvsg3GlpL5b6-Pjk5UBgXCGrL6U,30994
|
|
@@ -258,12 +258,12 @@ opentrons/protocol_api/core/engine/point_calculations.py,sha256=C2eF0fvJQGMqQv3D
|
|
|
258
258
|
opentrons/protocol_api/core/engine/protocol.py,sha256=_1gdg4lq2B21LWV9Tqb924E39HCPCgozAHxaCRGDSIk,46759
|
|
259
259
|
opentrons/protocol_api/core/engine/robot.py,sha256=o252HrC11tmZ5LRKT6NwXCoTeqcQFXHeNjszfxbJHjo,5366
|
|
260
260
|
opentrons/protocol_api/core/engine/stringify.py,sha256=GwFgEhFMk-uPfFQhQG_2mkaf4cxaItiY8RW7rZwiooQ,2794
|
|
261
|
-
opentrons/protocol_api/core/engine/transfer_components_executor.py,sha256=
|
|
261
|
+
opentrons/protocol_api/core/engine/transfer_components_executor.py,sha256=96SsftBRPB5WCeChLokXkdPJcmjP8FqlXEZXhNAlZKA,37582
|
|
262
262
|
opentrons/protocol_api/core/engine/well.py,sha256=PEtDwdC8NkHjqasJaDaVDtzc_WFw-qv5Lf7IU1DkrLY,7570
|
|
263
263
|
opentrons/protocol_api/core/legacy/__init__.py,sha256=_9jCJNKG3SlS_vljVu8HHkZmtLf4F-f-JHALLF5d5go,401
|
|
264
264
|
opentrons/protocol_api/core/legacy/deck.py,sha256=qHqcGo-Kdkl9L1aOE0pwrm9tsAnwkXbt4rIOr_VEP-s,13955
|
|
265
265
|
opentrons/protocol_api/core/legacy/labware_offset_provider.py,sha256=2DLIby9xmUrwLb2ht8hZbvNTxqPhNzWijd7yCb2cqP8,3783
|
|
266
|
-
opentrons/protocol_api/core/legacy/legacy_instrument_core.py,sha256=
|
|
266
|
+
opentrons/protocol_api/core/legacy/legacy_instrument_core.py,sha256=k-aM8Eu4qHqfhb8iRyn3jn3dZOGiq8JzQes8YUwY2v4,26501
|
|
267
267
|
opentrons/protocol_api/core/legacy/legacy_labware_core.py,sha256=WQOgtMlq--zv0Ch7mmraYr9rQBT4ie2zHqwgamBq9J8,8606
|
|
268
268
|
opentrons/protocol_api/core/legacy/legacy_module_core.py,sha256=tUhj88NKBMjCmCg6wjh1e2HX4d5hxjh8ZeJiYXaTaGY,23111
|
|
269
269
|
opentrons/protocol_api/core/legacy/legacy_protocol_core.py,sha256=ZIFC7W6YA61oWWkq5xYGTcI_2S2pmALz16uB1R8HVyQ,23670
|
|
@@ -272,7 +272,7 @@ opentrons/protocol_api/core/legacy/load_info.py,sha256=r-WaH5ZJb3TRCp_zvbMMh0P4B
|
|
|
272
272
|
opentrons/protocol_api/core/legacy/module_geometry.py,sha256=lvWFHZ81-JFw-1VZUW1R3yUIb59xpXT6H3jwlRintRo,21082
|
|
273
273
|
opentrons/protocol_api/core/legacy/well_geometry.py,sha256=n5bEsvYZXXTAqYSAqlXd5t40bUPPrJ2Oj2frBZafQHA,4719
|
|
274
274
|
opentrons/protocol_api/core/legacy_simulator/__init__.py,sha256=m9bLHGDJ6LSYC2WPm8tpOuu0zWSOPIrlybQgjRQBw9k,647
|
|
275
|
-
opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py,sha256=
|
|
275
|
+
opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py,sha256=pV-PEw8jMcdXMi2U5yplIkFJUUlugXd7OMJl_riiVf4,22940
|
|
276
276
|
opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py,sha256=28HrrHzeUfnGKXpZqQ-VM8WbPiadqVhKj2S9y33q6Lo,2910
|
|
277
277
|
opentrons/protocol_engine/__init__.py,sha256=UPSk7MbidkiSH_h4V3yxMvyTePKpRr5DM9-wfkJrlSo,4094
|
|
278
278
|
opentrons/protocol_engine/create_protocol_engine.py,sha256=tfDIsC7_JKlRiCXPB_8tuxRsssU6o0ViRmWbGPtX9QA,7582
|
|
@@ -294,7 +294,7 @@ opentrons/protocol_engine/commands/__init__.py,sha256=b073p4seq9bnyqMydVrYl9b_yC
|
|
|
294
294
|
opentrons/protocol_engine/commands/air_gap_in_place.py,sha256=Z1Tz2wFtEnlJBf_0xW0tEvX1yYJbA8ZmdZcHG_YIKwE,5387
|
|
295
295
|
opentrons/protocol_engine/commands/aspirate.py,sha256=ZxpwQ5Zq-AS11aNfgxx6PsL_MrBEaawAxAi7jWwpIRE,7920
|
|
296
296
|
opentrons/protocol_engine/commands/aspirate_in_place.py,sha256=vJiLSZqEzuMj-kuQESZevHM5g9brXAo159GhaFyEQm8,6530
|
|
297
|
-
opentrons/protocol_engine/commands/aspirate_while_tracking.py,sha256=
|
|
297
|
+
opentrons/protocol_engine/commands/aspirate_while_tracking.py,sha256=se1GIJWxxVCmvYQF_nhJK11D54KKeoN5Yne5Sti93-A,7224
|
|
298
298
|
opentrons/protocol_engine/commands/blow_out.py,sha256=3gboq4x5S8fq7j4ZZGNClXFDlOjcdW1v2g58GPdhaPI,4338
|
|
299
299
|
opentrons/protocol_engine/commands/blow_out_in_place.py,sha256=jm2XXyJfIP9-AAFwXhD59_13nX18-i6QqpLGb-lK7sI,3391
|
|
300
300
|
opentrons/protocol_engine/commands/command.py,sha256=1hWH_KWg_WDL4R4VXe1ZH2vO6pYt5SA-ZpuPPF1oV5E,10149
|
|
@@ -305,7 +305,7 @@ opentrons/protocol_engine/commands/configure_nozzle_layout.py,sha256=M_s5Ee03a7s
|
|
|
305
305
|
opentrons/protocol_engine/commands/custom.py,sha256=vOJc7QSNnYTpLvJm98OfDKjgcvVFRZs1eEKEd9WkPN0,2157
|
|
306
306
|
opentrons/protocol_engine/commands/dispense.py,sha256=gmjBXgGuWhB-SEUboXiNHqkaUrmpRTqSN4Dy362ln8w,6444
|
|
307
307
|
opentrons/protocol_engine/commands/dispense_in_place.py,sha256=gcj0HXUkPrU3Qz_DbWzP3XZHuB8tXSMTo9CFoGi25lw,6263
|
|
308
|
-
opentrons/protocol_engine/commands/dispense_while_tracking.py,sha256=
|
|
308
|
+
opentrons/protocol_engine/commands/dispense_while_tracking.py,sha256=vn0nw5D4ggpTEwarConFPHUVI4gNShehs5v1U5Kn9sY,6644
|
|
309
309
|
opentrons/protocol_engine/commands/drop_tip.py,sha256=ZZ63IoiT4dgWcemAHhNQfV4DUhkl-ToJyTRTxIiyAkc,7895
|
|
310
310
|
opentrons/protocol_engine/commands/drop_tip_in_place.py,sha256=gwSNEKBwds7kOTucXKSK74ozrDe7Cqhta7NR6IqKV3g,7062
|
|
311
311
|
opentrons/protocol_engine/commands/generate_command_schema.py,sha256=21Al_XQyRMNTb2ssVaxcNSPlgreOsCtKVXf8kZgpvR4,2296
|
|
@@ -418,7 +418,7 @@ opentrons/protocol_engine/execution/hardware_stopper.py,sha256=wlIl7U3gvnOiCvwri
|
|
|
418
418
|
opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py,sha256=BSFLzSSeELAYZCrCUfJZx5DdlrwU06Ur92TYd0T-hzM,9084
|
|
419
419
|
opentrons/protocol_engine/execution/labware_movement.py,sha256=XYVcxZOQ6_udpxcpwkIJpVD8-lgWLhizJAcRD9BclIo,12247
|
|
420
420
|
opentrons/protocol_engine/execution/movement.py,sha256=AWcj7xlrOh3QrvoaH0sZO63yrPCEc7VE8hKfJNxxtP0,12527
|
|
421
|
-
opentrons/protocol_engine/execution/pipetting.py,sha256=
|
|
421
|
+
opentrons/protocol_engine/execution/pipetting.py,sha256=0_OAxIDCQBAtdNOvdYwV0kEYfJvUrJWr-63rocqf75E,22094
|
|
422
422
|
opentrons/protocol_engine/execution/queue_worker.py,sha256=riVVywKIOQ3Lx-woFuuSqqBtfeKFt23nCUnsk7gSVoI,2860
|
|
423
423
|
opentrons/protocol_engine/execution/rail_lights.py,sha256=eiJT6oI_kFk7rFuFkZzISZiLNnpf7Kkh86Kyk9wQ_Jo,590
|
|
424
424
|
opentrons/protocol_engine/execution/run_control.py,sha256=ksvI2zkguC4G3lR3HJgAF8uY1PNcaRfi7UOYu-oIZgo,1144
|
|
@@ -580,11 +580,12 @@ opentrons/util/entrypoint_util.py,sha256=C0KN-09_WgNkqLbCyIB3yVm-kJoe7RGrZTb7qh9
|
|
|
580
580
|
opentrons/util/get_union_elements.py,sha256=H1KqLnG1zYvI2kanhc3MXRZT-S07E5a2vF1jEkhXpCs,1073
|
|
581
581
|
opentrons/util/helpers.py,sha256=3hr801bWGbxEcOFAS7f-iOhmnUhoK5qahbB8SIvaCfY,165
|
|
582
582
|
opentrons/util/linal.py,sha256=IlKAP9HkNBBgULeSf4YVwSKHdx9jnCjSr7nvDvlRALg,5753
|
|
583
|
-
opentrons/util/logging_config.py,sha256=
|
|
583
|
+
opentrons/util/logging_config.py,sha256=7et4YYuQdWdq_e50U-8vFS_QyNBRgdnqPGAQJm8qrIo,9954
|
|
584
|
+
opentrons/util/logging_queue_handler.py,sha256=ZsSJwy-oV8DXwpYiZisQ1PbYwmK2cOslD46AcyJ1E4I,2484
|
|
584
585
|
opentrons/util/performance_helpers.py,sha256=ew7H8XD20iS6-2TJAzbQeyzStZkkE6PzHt_Adx3wbZQ,5172
|
|
585
|
-
opentrons-8.4.
|
|
586
|
-
opentrons-8.4.
|
|
587
|
-
opentrons-8.4.
|
|
588
|
-
opentrons-8.4.
|
|
589
|
-
opentrons-8.4.
|
|
590
|
-
opentrons-8.4.
|
|
586
|
+
opentrons-8.4.0a5.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
587
|
+
opentrons-8.4.0a5.dist-info/METADATA,sha256=dVv_Zex7NX_Yf6tBwmhxcaTUWKOKDvYAmuqkq99qwEU,5084
|
|
588
|
+
opentrons-8.4.0a5.dist-info/WHEEL,sha256=qUzzGenXXuJTzyjFah76kDVqDvnk-YDzY00svnrl84w,109
|
|
589
|
+
opentrons-8.4.0a5.dist-info/entry_points.txt,sha256=fTa6eGCYkvOtv0ov-KVE8LLGetgb35LQLF9x85OWPVw,106
|
|
590
|
+
opentrons-8.4.0a5.dist-info/top_level.txt,sha256=wk6whpbMZdBQpcK0Fg0YVfUGrAgVOFON7oQAhOMGMW8,10
|
|
591
|
+
opentrons-8.4.0a5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|