opentrons 8.7.0a1__py3-none-any.whl → 8.7.0a2__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.

Files changed (119) hide show
  1. opentrons/_version.py +2 -2
  2. opentrons/drivers/thermocycler/abstract.py +1 -0
  3. opentrons/drivers/thermocycler/driver.py +33 -4
  4. opentrons/drivers/thermocycler/simulator.py +2 -0
  5. opentrons/hardware_control/api.py +24 -5
  6. opentrons/hardware_control/backends/controller.py +8 -2
  7. opentrons/hardware_control/backends/ot3controller.py +3 -0
  8. opentrons/hardware_control/backends/ot3simulator.py +2 -1
  9. opentrons/hardware_control/backends/simulator.py +2 -1
  10. opentrons/hardware_control/backends/subsystem_manager.py +5 -2
  11. opentrons/hardware_control/module_control.py +82 -8
  12. opentrons/hardware_control/modules/__init__.py +3 -0
  13. opentrons/hardware_control/modules/absorbance_reader.py +11 -4
  14. opentrons/hardware_control/modules/flex_stacker.py +38 -9
  15. opentrons/hardware_control/modules/heater_shaker.py +30 -5
  16. opentrons/hardware_control/modules/magdeck.py +8 -4
  17. opentrons/hardware_control/modules/mod_abc.py +13 -5
  18. opentrons/hardware_control/modules/tempdeck.py +25 -5
  19. opentrons/hardware_control/modules/thermocycler.py +56 -10
  20. opentrons/hardware_control/modules/types.py +20 -1
  21. opentrons/hardware_control/modules/utils.py +11 -4
  22. opentrons/hardware_control/nozzle_manager.py +3 -0
  23. opentrons/hardware_control/ot3api.py +26 -5
  24. opentrons/hardware_control/scripts/update_module_fw.py +5 -0
  25. opentrons/hardware_control/types.py +31 -2
  26. opentrons/legacy_commands/protocol_commands.py +20 -0
  27. opentrons/legacy_commands/types.py +42 -0
  28. opentrons/motion_planning/waypoints.py +15 -29
  29. opentrons/protocol_api/__init__.py +5 -0
  30. opentrons/protocol_api/_types.py +6 -1
  31. opentrons/protocol_api/core/common.py +3 -1
  32. opentrons/protocol_api/core/engine/_default_labware_versions.py +32 -11
  33. opentrons/protocol_api/core/engine/labware.py +8 -1
  34. opentrons/protocol_api/core/engine/module_core.py +4 -0
  35. opentrons/protocol_api/core/engine/protocol.py +18 -1
  36. opentrons/protocol_api/core/engine/tasks.py +35 -0
  37. opentrons/protocol_api/core/legacy/legacy_module_core.py +2 -0
  38. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +11 -1
  39. opentrons/protocol_api/core/legacy/tasks.py +19 -0
  40. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
  41. opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
  42. opentrons/protocol_api/core/module.py +1 -0
  43. opentrons/protocol_api/core/protocol.py +11 -2
  44. opentrons/protocol_api/core/tasks.py +31 -0
  45. opentrons/protocol_api/module_contexts.py +1 -0
  46. opentrons/protocol_api/protocol_context.py +26 -4
  47. opentrons/protocol_api/robot_context.py +38 -21
  48. opentrons/protocol_api/tasks.py +48 -0
  49. opentrons/protocol_api/validation.py +6 -1
  50. opentrons/protocol_engine/actions/__init__.py +4 -2
  51. opentrons/protocol_engine/actions/actions.py +22 -9
  52. opentrons/protocol_engine/clients/sync_client.py +6 -7
  53. opentrons/protocol_engine/commands/__init__.py +42 -0
  54. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
  55. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
  56. opentrons/protocol_engine/commands/aspirate.py +1 -0
  57. opentrons/protocol_engine/commands/command.py +1 -0
  58. opentrons/protocol_engine/commands/command_unions.py +39 -0
  59. opentrons/protocol_engine/commands/create_timer.py +83 -0
  60. opentrons/protocol_engine/commands/dispense.py +1 -0
  61. opentrons/protocol_engine/commands/drop_tip.py +32 -8
  62. opentrons/protocol_engine/commands/movement_common.py +2 -0
  63. opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
  64. opentrons/protocol_engine/commands/set_tip_state.py +97 -0
  65. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
  66. opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
  67. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +17 -1
  68. opentrons/protocol_engine/commands/touch_tip.py +1 -1
  69. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
  70. opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
  71. opentrons/protocol_engine/errors/__init__.py +4 -0
  72. opentrons/protocol_engine/errors/exceptions.py +55 -0
  73. opentrons/protocol_engine/execution/__init__.py +2 -0
  74. opentrons/protocol_engine/execution/command_executor.py +8 -0
  75. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  76. opentrons/protocol_engine/execution/labware_movement.py +9 -12
  77. opentrons/protocol_engine/execution/movement.py +2 -0
  78. opentrons/protocol_engine/execution/queue_worker.py +4 -0
  79. opentrons/protocol_engine/execution/run_control.py +8 -0
  80. opentrons/protocol_engine/execution/task_handler.py +157 -0
  81. opentrons/protocol_engine/protocol_engine.py +67 -33
  82. opentrons/protocol_engine/resources/__init__.py +2 -0
  83. opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
  84. opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
  85. opentrons/protocol_engine/resources/labware_validation.py +10 -6
  86. opentrons/protocol_engine/state/_well_math.py +60 -18
  87. opentrons/protocol_engine/state/addressable_areas.py +2 -0
  88. opentrons/protocol_engine/state/commands.py +7 -7
  89. opentrons/protocol_engine/state/geometry.py +204 -374
  90. opentrons/protocol_engine/state/labware.py +52 -102
  91. opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
  92. opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1331 -0
  93. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
  94. opentrons/protocol_engine/state/modules.py +21 -8
  95. opentrons/protocol_engine/state/motion.py +44 -0
  96. opentrons/protocol_engine/state/state.py +14 -0
  97. opentrons/protocol_engine/state/state_summary.py +2 -0
  98. opentrons/protocol_engine/state/tasks.py +139 -0
  99. opentrons/protocol_engine/state/tips.py +177 -258
  100. opentrons/protocol_engine/state/update_types.py +16 -9
  101. opentrons/protocol_engine/types/__init__.py +9 -3
  102. opentrons/protocol_engine/types/deck_configuration.py +5 -1
  103. opentrons/protocol_engine/types/instrument.py +8 -1
  104. opentrons/protocol_engine/types/labware.py +1 -13
  105. opentrons/protocol_engine/types/module.py +10 -0
  106. opentrons/protocol_engine/types/tasks.py +38 -0
  107. opentrons/protocol_engine/types/tip.py +9 -0
  108. opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
  109. opentrons/protocol_runner/run_orchestrator.py +18 -2
  110. opentrons/protocols/api_support/definitions.py +1 -1
  111. opentrons/protocols/api_support/types.py +2 -1
  112. opentrons/simulate.py +48 -15
  113. opentrons/system/camera.py +1 -1
  114. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/METADATA +4 -4
  115. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/RECORD +118 -105
  116. opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
  117. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/WHEEL +0 -0
  118. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/entry_points.txt +0 -0
  119. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -267,6 +267,22 @@ from .wait_for_duration import (
267
267
  WaitForDurationCommandType,
268
268
  )
269
269
 
270
+ from .create_timer import (
271
+ CreateTimer,
272
+ CreateTimerCreate,
273
+ CreateTimerParams,
274
+ CreateTimerResult,
275
+ CreateTimerCommandType,
276
+ )
277
+
278
+ from .wait_for_tasks import (
279
+ WaitForTasks,
280
+ WaitForTasksCreate,
281
+ WaitForTasksParams,
282
+ WaitForTasksResult,
283
+ WaitForTasksCommandType,
284
+ )
285
+
270
286
  from .pick_up_tip import (
271
287
  PickUpTip,
272
288
  PickUpTipParams,
@@ -372,6 +388,14 @@ from .get_next_tip import (
372
388
  GetNextTipCommandType,
373
389
  )
374
390
 
391
+ from .set_tip_state import (
392
+ SetTipState,
393
+ SetTipStateCreate,
394
+ SetTipStateParams,
395
+ SetTipStateResult,
396
+ SetTipStateCommandType,
397
+ )
398
+
375
399
  from .liquid_probe import (
376
400
  LiquidProbe,
377
401
  LiquidProbeParams,
@@ -454,6 +478,8 @@ Command = Annotated[
454
478
  PrepareToAspirate,
455
479
  WaitForResume,
456
480
  WaitForDuration,
481
+ WaitForTasks,
482
+ CreateTimer,
457
483
  PickUpTip,
458
484
  SavePosition,
459
485
  SetRailLights,
@@ -462,6 +488,7 @@ Command = Annotated[
462
488
  VerifyTipPresence,
463
489
  GetTipPresence,
464
490
  GetNextTip,
491
+ SetTipState,
465
492
  LiquidProbe,
466
493
  TryLiquidProbe,
467
494
  SealPipetteToTip,
@@ -557,6 +584,8 @@ CommandParams = Union[
557
584
  PrepareToAspirateParams,
558
585
  WaitForResumeParams,
559
586
  WaitForDurationParams,
587
+ WaitForTasksParams,
588
+ CreateTimerParams,
560
589
  PickUpTipParams,
561
590
  SavePositionParams,
562
591
  SetRailLightsParams,
@@ -565,6 +594,7 @@ CommandParams = Union[
565
594
  VerifyTipPresenceParams,
566
595
  GetTipPresenceParams,
567
596
  GetNextTipParams,
597
+ SetTipStateParams,
568
598
  LiquidProbeParams,
569
599
  TryLiquidProbeParams,
570
600
  SealPipetteToTipParams,
@@ -658,6 +688,8 @@ CommandType = Union[
658
688
  PrepareToAspirateCommandType,
659
689
  WaitForResumeCommandType,
660
690
  WaitForDurationCommandType,
691
+ WaitForTasksCommandType,
692
+ CreateTimerCommandType,
661
693
  PickUpTipCommandType,
662
694
  SavePositionCommandType,
663
695
  SetRailLightsCommandType,
@@ -666,6 +698,7 @@ CommandType = Union[
666
698
  VerifyTipPresenceCommandType,
667
699
  GetTipPresenceCommandType,
668
700
  GetNextTipCommandType,
701
+ SetTipStateCommandType,
669
702
  LiquidProbeCommandType,
670
703
  TryLiquidProbeCommandType,
671
704
  SealPipetteToTipCommandType,
@@ -760,6 +793,8 @@ CommandCreate = Annotated[
760
793
  PrepareToAspirateCreate,
761
794
  WaitForResumeCreate,
762
795
  WaitForDurationCreate,
796
+ WaitForTasksCreate,
797
+ CreateTimerCreate,
763
798
  PickUpTipCreate,
764
799
  SavePositionCreate,
765
800
  SetRailLightsCreate,
@@ -768,6 +803,7 @@ CommandCreate = Annotated[
768
803
  VerifyTipPresenceCreate,
769
804
  GetTipPresenceCreate,
770
805
  GetNextTipCreate,
806
+ SetTipStateCreate,
771
807
  LiquidProbeCreate,
772
808
  TryLiquidProbeCreate,
773
809
  SealPipetteToTipCreate,
@@ -870,6 +906,8 @@ CommandResult = Union[
870
906
  PrepareToAspirateResult,
871
907
  WaitForResumeResult,
872
908
  WaitForDurationResult,
909
+ WaitForTasksResult,
910
+ CreateTimerResult,
873
911
  PickUpTipResult,
874
912
  SavePositionResult,
875
913
  SetRailLightsResult,
@@ -878,6 +916,7 @@ CommandResult = Union[
878
916
  VerifyTipPresenceResult,
879
917
  GetTipPresenceResult,
880
918
  GetNextTipResult,
919
+ SetTipStateResult,
881
920
  LiquidProbeResult,
882
921
  TryLiquidProbeResult,
883
922
  SealPipetteToTipResult,
@@ -0,0 +1,83 @@
1
+ """CreateTimer command request, result, and implementation models."""
2
+
3
+ from __future__ import annotations
4
+ from pydantic import BaseModel, Field
5
+ from typing import Optional, Type, TYPE_CHECKING
6
+ from typing_extensions import Literal
7
+
8
+ from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
9
+ from ..errors.error_occurrence import ErrorOccurrence
10
+
11
+ if TYPE_CHECKING:
12
+ from ..execution import TaskHandler, RunControlHandler
13
+
14
+
15
+ CreateTimerCommandType = Literal["createTimer"]
16
+
17
+
18
+ class CreateTimerParams(BaseModel):
19
+ """Payload required to annotate execution with a CreateTimer."""
20
+
21
+ time: float = Field(
22
+ ...,
23
+ description="The time before the timer should elapse in seconds. This is the minimum time before the timer elapses; it may in practice take longer than this.",
24
+ )
25
+ task_id: str | None = Field(
26
+ None,
27
+ description="The id of the timer task",
28
+ )
29
+
30
+
31
+ class CreateTimerResult(BaseModel):
32
+ """Result data from the execution of a CreateTimer command."""
33
+
34
+ task_id: str = Field(..., description="The id of the timer task")
35
+ time: float = Field(..., description="The same time as the parameter.")
36
+
37
+
38
+ class CreateTimerImplementation(
39
+ AbstractCommandImpl[CreateTimerParams, SuccessData[CreateTimerResult]]
40
+ ):
41
+ """CreateTimer command implementation."""
42
+
43
+ def __init__(
44
+ self,
45
+ task_handler: TaskHandler,
46
+ run_control: RunControlHandler,
47
+ **kwargs: object,
48
+ ) -> None:
49
+ self._task_handler = task_handler
50
+ self._run_control = run_control
51
+
52
+ async def execute(
53
+ self, params: CreateTimerParams
54
+ ) -> SuccessData[CreateTimerResult]:
55
+ """No operation taken other than capturing message in command."""
56
+
57
+ async def timer(task_handler: TaskHandler) -> None:
58
+ async with task_handler.synchronize_concurrent("createTimer"):
59
+ await self._run_control.wait_for_duration(params.time)
60
+
61
+ task = await self._task_handler.create_task(timer, params.task_id)
62
+ return SuccessData(
63
+ public=CreateTimerResult(task_id=task.id, time=params.time),
64
+ )
65
+
66
+
67
+ class CreateTimer(BaseCommand[CreateTimerParams, CreateTimerResult, ErrorOccurrence]):
68
+ """CreateTimer command model."""
69
+
70
+ commandType: CreateTimerCommandType = "createTimer"
71
+ params: CreateTimerParams
72
+ result: Optional[CreateTimerResult] = None
73
+
74
+ _ImplementationCls: Type[CreateTimerImplementation] = CreateTimerImplementation
75
+
76
+
77
+ class CreateTimerCreate(BaseCommandCreate[CreateTimerParams]):
78
+ """CreateTimer command request model."""
79
+
80
+ commandType: CreateTimerCommandType = "createTimer"
81
+ params: CreateTimerParams
82
+
83
+ _CommandCls: Type[CreateTimer] = CreateTimer
@@ -101,6 +101,7 @@ class DispenseImplementation(AbstractCommandImpl[DispenseParams, _ExecuteReturn]
101
101
  well_name=well_name,
102
102
  well_location=well_location,
103
103
  operation_volume=volume,
104
+ offset_pipette_for_reservoir_subwells=False,
104
105
  )
105
106
  if isinstance(move_result, DefinedErrorData):
106
107
  return move_result
@@ -12,7 +12,7 @@ from opentrons.protocol_engine.errors.exceptions import TipAttachedError
12
12
  from opentrons.protocol_engine.resources.model_utils import ModelUtils
13
13
 
14
14
  from ..state.update_types import StateUpdate
15
- from ..types import DropTipWellLocation
15
+ from ..types import DropTipWellLocation, TipRackWellState
16
16
  from .pipetting_common import (
17
17
  PipetteIdMixin,
18
18
  TipPhysicallyAttachedError,
@@ -140,6 +140,25 @@ class DropTipImplementation(AbstractCommandImpl[DropTipParams, _ExecuteReturn]):
140
140
  partially_configured=is_partially_configured,
141
141
  )
142
142
 
143
+ is_tip_rack = self._state_view.labware.get_definition(
144
+ labware_id
145
+ ).parameters.isTiprack
146
+
147
+ # It's possible that we are dropping tips into a labware trash for pre API v2.14 OT-2 protocols
148
+ # (or something else unexpected), so if it is not a tip rack mark no wells as used
149
+ if is_tip_rack:
150
+ tips_to_mark_as_used = (
151
+ self._state_view.tips.compute_tips_to_mark_as_used_or_empty(
152
+ labware_id=labware_id,
153
+ well_name=well_name,
154
+ nozzle_map=self._state_view.pipettes.get_nozzle_configuration(
155
+ pipette_id
156
+ ),
157
+ )
158
+ )
159
+ else:
160
+ tips_to_mark_as_used = []
161
+
143
162
  move_result = await move_to_well(
144
163
  movement=self._movement_handler,
145
164
  model_utils=self._model_utils,
@@ -152,12 +171,7 @@ class DropTipImplementation(AbstractCommandImpl[DropTipParams, _ExecuteReturn]):
152
171
  return move_result
153
172
 
154
173
  scrape_type = TipScrapeType.NONE
155
- if (
156
- params.scrape_tips
157
- and self._state_view.geometry._labware.get_definition(
158
- labware_id
159
- ).parameters.isTiprack
160
- ):
174
+ if params.scrape_tips and is_tip_rack:
161
175
  if int("".join(filter(str.isdigit, well_name))) <= 6:
162
176
  scrape_type = TipScrapeType.RIGHT_ONE_COL
163
177
  else:
@@ -194,6 +208,10 @@ class DropTipImplementation(AbstractCommandImpl[DropTipParams, _ExecuteReturn]):
194
208
  pipette_id=params.pipetteId,
195
209
  tip_geometry=None,
196
210
  tip_source=None,
211
+ ).update_tip_rack_well_state(
212
+ tip_state=TipRackWellState.USED,
213
+ labware_id=labware_id,
214
+ well_names=tips_to_mark_as_used,
197
215
  ),
198
216
  )
199
217
  else:
@@ -201,10 +219,16 @@ class DropTipImplementation(AbstractCommandImpl[DropTipParams, _ExecuteReturn]):
201
219
  public=DropTipResult(position=move_result.public.position),
202
220
  state_update=move_result.state_update.set_fluid_unknown(
203
221
  pipette_id=pipette_id
204
- ).update_pipette_tip_state(
222
+ )
223
+ .update_pipette_tip_state(
205
224
  pipette_id=params.pipetteId,
206
225
  tip_geometry=None,
207
226
  tip_source=None,
227
+ )
228
+ .update_tip_rack_well_state(
229
+ tip_state=TipRackWellState.USED,
230
+ labware_id=labware_id,
231
+ well_names=tips_to_mark_as_used,
208
232
  ),
209
233
  )
210
234
 
@@ -152,6 +152,7 @@ async def move_to_well(
152
152
  minimum_z_height: Optional[float] = None,
153
153
  speed: Optional[float] = None,
154
154
  operation_volume: Optional[float] = None,
155
+ offset_pipette_for_reservoir_subwells: bool = False,
155
156
  ) -> MoveToWellOperationReturn:
156
157
  """Execute a move to well microoperation."""
157
158
  try:
@@ -165,6 +166,7 @@ async def move_to_well(
165
166
  minimum_z_height=minimum_z_height,
166
167
  speed=speed,
167
168
  operation_volume=operation_volume,
169
+ offset_pipette_for_reservoir_subwells=offset_pipette_for_reservoir_subwells,
168
170
  )
169
171
  except StallOrCollisionDetectedError as e:
170
172
  return DefinedErrorData(
@@ -10,7 +10,7 @@ from typing_extensions import Literal
10
10
  from ..errors import ErrorOccurrence, PickUpTipTipNotAttachedError
11
11
  from ..resources import ModelUtils
12
12
  from ..state import update_types
13
- from ..types import PickUpTipWellLocation, LabwareWellId
13
+ from ..types import PickUpTipWellLocation, LabwareWellId, TipRackWellState
14
14
  from .pipetting_common import (
15
15
  PipetteIdMixin,
16
16
  )
@@ -121,10 +121,14 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
121
121
  labware_id = params.labwareId
122
122
  well_name = params.wellName
123
123
 
124
- tips_to_mark_as_used = self._state_view.tips.compute_tips_to_mark_as_used(
125
- labware_id=labware_id,
126
- well_name=well_name,
127
- nozzle_map=self._state_view.pipettes.get_nozzle_configuration(pipette_id),
124
+ tips_to_mark_as_empty = (
125
+ self._state_view.tips.compute_tips_to_mark_as_used_or_empty(
126
+ labware_id=labware_id,
127
+ well_name=well_name,
128
+ nozzle_map=self._state_view.pipettes.get_nozzle_configuration(
129
+ pipette_id
130
+ ),
131
+ )
128
132
  )
129
133
 
130
134
  well_location = self._state_view.geometry.convert_pick_up_tip_well_location(
@@ -160,16 +164,20 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
160
164
  ),
161
165
  )
162
166
  .set_fluid_empty(pipette_id=pipette_id, clean_tip=True)
163
- .mark_tips_as_used(
164
- labware_id=labware_id, well_names=tips_to_mark_as_used
167
+ .update_tip_rack_well_state(
168
+ tip_state=TipRackWellState.EMPTY,
169
+ labware_id=labware_id,
170
+ well_names=tips_to_mark_as_empty,
165
171
  )
166
172
  )
167
173
  state_update = (
168
174
  update_types.StateUpdate.reduce(
169
175
  update_types.StateUpdate(), move_result.state_update
170
176
  )
171
- .mark_tips_as_used(
172
- labware_id=labware_id, well_names=tips_to_mark_as_used
177
+ .update_tip_rack_well_state(
178
+ tip_state=TipRackWellState.EMPTY,
179
+ labware_id=labware_id,
180
+ well_names=tips_to_mark_as_empty,
173
181
  )
174
182
  .set_fluid_unknown(pipette_id=pipette_id)
175
183
  )
@@ -197,8 +205,10 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
197
205
  labware_id=labware_id, well_name=well_name
198
206
  ),
199
207
  )
200
- .mark_tips_as_used(
201
- labware_id=labware_id, well_names=tips_to_mark_as_used
208
+ .update_tip_rack_well_state(
209
+ tip_state=TipRackWellState.EMPTY,
210
+ labware_id=labware_id,
211
+ well_names=tips_to_mark_as_empty,
202
212
  )
203
213
  .set_fluid_empty(pipette_id=pipette_id, clean_tip=True)
204
214
  .set_pipette_ready_to_aspirate(
@@ -0,0 +1,97 @@
1
+ """Set tip state command request, result, and implementation models."""
2
+
3
+ from __future__ import annotations
4
+ from pydantic import BaseModel, Field
5
+ from typing import TYPE_CHECKING, Optional, Type, List
6
+ from typing_extensions import Literal
7
+
8
+ from ..errors.error_occurrence import ErrorOccurrence
9
+ from ..state.update_types import StateUpdate
10
+ from ..types import TipRackWellState
11
+
12
+
13
+ from .command import (
14
+ AbstractCommandImpl,
15
+ BaseCommand,
16
+ BaseCommandCreate,
17
+ SuccessData,
18
+ )
19
+
20
+ if TYPE_CHECKING:
21
+ from ..state.state import StateView
22
+
23
+
24
+ SetTipStateCommandType = Literal["setTipState"]
25
+
26
+
27
+ class SetTipStateParams(BaseModel):
28
+ """Payload needed to set tip wells of a tip rack to the requested state."""
29
+
30
+ labwareId: str = Field(
31
+ ..., description="Identifier of tip rack labware to set tip wells in."
32
+ )
33
+ wellNames: List[str] = Field(
34
+ ..., description="Names of the well to set tip well state for."
35
+ )
36
+ tipWellState: TipRackWellState = Field(
37
+ ..., description="State to set tip wells to."
38
+ )
39
+
40
+
41
+ class SetTipStateResult(BaseModel):
42
+ """Result data from the execution of a setTipState command."""
43
+
44
+ pass
45
+
46
+
47
+ class SetTipStateImplementation(
48
+ AbstractCommandImpl[SetTipStateParams, SuccessData[SetTipStateResult]]
49
+ ):
50
+ """Set tip state command implementation."""
51
+
52
+ def __init__(
53
+ self,
54
+ state_view: StateView,
55
+ **kwargs: object,
56
+ ) -> None:
57
+ self._state_view = state_view
58
+
59
+ async def execute(
60
+ self, params: SetTipStateParams
61
+ ) -> SuccessData[SetTipStateResult]:
62
+ """Set the tip rack wells to the requested state."""
63
+ labware_id = params.labwareId
64
+ well_names = params.wellNames
65
+
66
+ self._state_view.labware.raise_if_not_tip_rack(labware_id=labware_id)
67
+ self._state_view.labware.raise_if_wells_are_invalid(
68
+ labware_id=labware_id, well_names=well_names
69
+ )
70
+
71
+ return SuccessData(
72
+ public=SetTipStateResult(),
73
+ state_update=StateUpdate().update_tip_rack_well_state(
74
+ tip_state=params.tipWellState,
75
+ labware_id=labware_id,
76
+ well_names=well_names,
77
+ ),
78
+ )
79
+
80
+
81
+ class SetTipState(BaseCommand[SetTipStateParams, SetTipStateResult, ErrorOccurrence]):
82
+ """Set tip state command model."""
83
+
84
+ commandType: SetTipStateCommandType = "setTipState"
85
+ params: SetTipStateParams
86
+ result: Optional[SetTipStateResult] = None
87
+
88
+ _ImplementationCls: Type[SetTipStateImplementation] = SetTipStateImplementation
89
+
90
+
91
+ class SetTipStateCreate(BaseCommandCreate[SetTipStateParams]):
92
+ """Set tip state command creation request model."""
93
+
94
+ commandType: SetTipStateCommandType = "setTipState"
95
+ params: SetTipStateParams
96
+
97
+ _CommandCls: Type[SetTipState] = SetTipState
@@ -33,6 +33,11 @@ class ProfileStep(BaseModel):
33
33
  holdSeconds: float = Field(
34
34
  ..., description="Time to hold target temperature in seconds."
35
35
  )
36
+ rampRate: float | SkipJsonSchema[None] = Field(
37
+ None,
38
+ description="How quickly to change temperature in °C/second.",
39
+ json_schema_extra=_remove_default,
40
+ )
36
41
 
37
42
 
38
43
  class ProfileCycle(BaseModel):
@@ -68,6 +73,7 @@ def _transform_profile_step(
68
73
  return ThermocyclerStep(
69
74
  temperature=thermocycler_state.validate_target_block_temperature(step.celsius),
70
75
  hold_time_seconds=step.holdSeconds,
76
+ ramp_rate=thermocycler_state.validate_ramp_rate(step.rampRate, step.celsius),
71
77
  )
72
78
 
73
79
 
@@ -30,6 +30,11 @@ class RunProfileStepParams(BaseModel):
30
30
  holdSeconds: float = Field(
31
31
  ..., description="Time to hold target temperature at in seconds."
32
32
  )
33
+ rampRate: float | SkipJsonSchema[None] = Field(
34
+ None,
35
+ description="How quickly to change temperature in °C/second.",
36
+ json_schema_extra=_remove_default,
37
+ )
33
38
 
34
39
 
35
40
  class RunProfileParams(BaseModel):
@@ -81,6 +86,9 @@ class RunProfileImpl(
81
86
  profile_step.celsius
82
87
  ),
83
88
  hold_time_seconds=profile_step.holdSeconds,
89
+ ramp_rate=thermocycler_state.validate_ramp_rate(
90
+ profile_step.rampRate, profile_step.celsius
91
+ ),
84
92
  )
85
93
  for profile_step in params.profile
86
94
  ]
@@ -39,6 +39,12 @@ class SetTargetBlockTemperatureParams(BaseModel):
39
39
  " the given hold time has elapsed.",
40
40
  json_schema_extra=_remove_default,
41
41
  )
42
+ ramp_rate: float | SkipJsonSchema[None] = Field(
43
+ None,
44
+ description="The rate in C°/second to change temperature from the current target."
45
+ " If unspecified, the Thermocycler will change temperature at the fastest possible rate.",
46
+ json_schema_extra=_remove_default,
47
+ )
42
48
 
43
49
 
44
50
  class SetTargetBlockTemperatureResult(BaseModel):
@@ -90,6 +96,13 @@ class SetTargetBlockTemperatureImpl(
90
96
  hold_time = thermocycler_state.validate_hold_time(params.holdTimeSeconds)
91
97
  else:
92
98
  hold_time = None
99
+ target_ramp_rate: Optional[float]
100
+ if params.ramp_rate is not None:
101
+ target_ramp_rate = thermocycler_state.validate_ramp_rate(
102
+ params.ramp_rate, target_temperature
103
+ )
104
+ else:
105
+ target_ramp_rate = None
93
106
 
94
107
  thermocycler_hardware = self._equipment.get_module_hardware_api(
95
108
  thermocycler_state.module_id
@@ -97,7 +110,10 @@ class SetTargetBlockTemperatureImpl(
97
110
 
98
111
  if thermocycler_hardware is not None:
99
112
  await thermocycler_hardware.set_target_block_temperature(
100
- target_temperature, volume=target_volume, hold_time_seconds=hold_time
113
+ target_temperature,
114
+ volume=target_volume,
115
+ hold_time_seconds=hold_time,
116
+ ramp_rate=target_ramp_rate,
101
117
  )
102
118
 
103
119
  return SuccessData(
@@ -114,7 +114,7 @@ class TouchTipImplementation(
114
114
 
115
115
  if self._state_view.labware.get_has_quirk(labware_id, "touchTipDisabled"):
116
116
  raise TouchTipDisabledError(
117
- f"Touch tip not allowed on labware {labware_id}"
117
+ f"Touch tip not allowed on labware {self._state_view.labware.get_display_name(labware_id)}"
118
118
  )
119
119
 
120
120
  if self._state_view.labware.is_tiprack(labware_id):
@@ -14,12 +14,11 @@ from opentrons.protocol_engine.errors.exceptions import (
14
14
  CannotPerformGripperAction,
15
15
  GripperNotAttachedError,
16
16
  )
17
- from opentrons.types import Point
18
-
19
17
  from ...types import (
20
18
  DeckSlotLocation,
21
19
  ModuleModel,
22
20
  OnDeckLabwareLocation,
21
+ GripperMoveType,
23
22
  )
24
23
  from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
25
24
  from ...errors.error_occurrence import ErrorOccurrence
@@ -100,22 +99,6 @@ class UnsafePlaceLabwareImplementation(
100
99
  LabwareUri(params.labwareURI)
101
100
  )
102
101
 
103
- # todo(mm, 2024-11-06): This is only correct in the special case of an
104
- # absorbance reader lid. Its definition currently puts the offsets for *itself*
105
- # in the property that's normally meant for offsets for its *children.*
106
- final_offsets = self._state_view.labware.get_child_gripper_offsets(
107
- labware_definition=definition, slot_name=None
108
- )
109
- drop_offset = (
110
- Point(
111
- final_offsets.dropOffset.x,
112
- final_offsets.dropOffset.y,
113
- final_offsets.dropOffset.z,
114
- )
115
- if final_offsets
116
- else None
117
- )
118
-
119
102
  if isinstance(params.location, DeckSlotLocation):
120
103
  self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
121
104
  params.location.slotName.id
@@ -139,7 +122,7 @@ class UnsafePlaceLabwareImplementation(
139
122
  await ot3api.update_axis_position_estimations([Axis.X, Axis.Y])
140
123
 
141
124
  # Place the labware down
142
- await self._start_movement(ot3api, definition, location, drop_offset)
125
+ await self._start_movement(ot3api, definition, location)
143
126
 
144
127
  return SuccessData(public=UnsafePlaceLabwareResult())
145
128
 
@@ -148,7 +131,6 @@ class UnsafePlaceLabwareImplementation(
148
131
  ot3api: OT3HardwareControlAPI,
149
132
  labware_definition: LabwareDefinition,
150
133
  location: OnDeckLabwareLocation,
151
- drop_offset: Optional[Point],
152
134
  ) -> None:
153
135
  gripper_homed_position = await ot3api.gantry_position(
154
136
  mount=OT3Mount.GRIPPER,
@@ -156,13 +138,15 @@ class UnsafePlaceLabwareImplementation(
156
138
  )
157
139
 
158
140
  to_labware_center = self._state_view.geometry.get_labware_grip_point(
159
- labware_definition=labware_definition, location=location
141
+ labware_definition=labware_definition,
142
+ location=location,
143
+ move_type=GripperMoveType.DROP_LABWARE,
144
+ user_additional_offset=None,
160
145
  )
161
146
 
162
147
  movement_waypoints = get_gripper_labware_placement_waypoints(
163
148
  to_labware_center=to_labware_center,
164
149
  gripper_home_z=gripper_homed_position.z,
165
- drop_offset=drop_offset,
166
150
  )
167
151
 
168
152
  # start movement