opentrons 8.2.0a3__py2.py3-none-any.whl → 8.3.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of opentrons might be problematic. Click here for more details.

Files changed (238) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/config/defaults_ot3.py +1 -0
  9. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  10. opentrons/drivers/asyncio/communication/errors.py +16 -3
  11. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  12. opentrons/drivers/command_builder.py +2 -2
  13. opentrons/drivers/flex_stacker/__init__.py +9 -0
  14. opentrons/drivers/flex_stacker/abstract.py +89 -0
  15. opentrons/drivers/flex_stacker/driver.py +260 -0
  16. opentrons/drivers/flex_stacker/simulator.py +109 -0
  17. opentrons/drivers/flex_stacker/types.py +138 -0
  18. opentrons/drivers/heater_shaker/driver.py +18 -3
  19. opentrons/drivers/temp_deck/driver.py +13 -3
  20. opentrons/drivers/thermocycler/driver.py +17 -3
  21. opentrons/execute.py +3 -1
  22. opentrons/hardware_control/__init__.py +1 -2
  23. opentrons/hardware_control/api.py +33 -21
  24. opentrons/hardware_control/backends/flex_protocol.py +17 -7
  25. opentrons/hardware_control/backends/ot3controller.py +213 -63
  26. opentrons/hardware_control/backends/ot3simulator.py +18 -9
  27. opentrons/hardware_control/backends/ot3utils.py +43 -15
  28. opentrons/hardware_control/dev_types.py +4 -0
  29. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  30. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  31. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  32. opentrons/hardware_control/emulation/settings.py +3 -4
  33. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  34. opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
  35. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  36. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  37. opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
  38. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  39. opentrons/hardware_control/modules/mod_abc.py +2 -2
  40. opentrons/hardware_control/motion_utilities.py +68 -0
  41. opentrons/hardware_control/nozzle_manager.py +39 -41
  42. opentrons/hardware_control/ot3_calibration.py +1 -1
  43. opentrons/hardware_control/ot3api.py +78 -31
  44. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  45. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  46. opentrons/hardware_control/protocols/liquid_handler.py +22 -1
  47. opentrons/hardware_control/protocols/motion_controller.py +7 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/legacy_commands/commands.py +37 -0
  51. opentrons/legacy_commands/types.py +39 -0
  52. opentrons/protocol_api/__init__.py +20 -1
  53. opentrons/protocol_api/_liquid.py +24 -49
  54. opentrons/protocol_api/_liquid_properties.py +754 -0
  55. opentrons/protocol_api/_types.py +24 -0
  56. opentrons/protocol_api/core/common.py +2 -0
  57. opentrons/protocol_api/core/engine/instrument.py +191 -10
  58. opentrons/protocol_api/core/engine/labware.py +29 -7
  59. opentrons/protocol_api/core/engine/protocol.py +130 -5
  60. opentrons/protocol_api/core/engine/robot.py +139 -0
  61. opentrons/protocol_api/core/engine/well.py +4 -1
  62. opentrons/protocol_api/core/instrument.py +73 -4
  63. opentrons/protocol_api/core/labware.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
  65. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  66. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  67. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  68. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
  69. opentrons/protocol_api/core/protocol.py +34 -1
  70. opentrons/protocol_api/core/robot.py +51 -0
  71. opentrons/protocol_api/instrument_context.py +299 -44
  72. opentrons/protocol_api/labware.py +248 -9
  73. opentrons/protocol_api/module_contexts.py +21 -17
  74. opentrons/protocol_api/protocol_context.py +125 -4
  75. opentrons/protocol_api/robot_context.py +204 -32
  76. opentrons/protocol_api/validation.py +262 -3
  77. opentrons/protocol_engine/__init__.py +4 -0
  78. opentrons/protocol_engine/actions/actions.py +2 -3
  79. opentrons/protocol_engine/clients/sync_client.py +18 -0
  80. opentrons/protocol_engine/commands/__init__.py +121 -0
  81. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
  82. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
  83. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
  84. opentrons/protocol_engine/commands/absorbance_reader/read.py +40 -10
  85. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  86. opentrons/protocol_engine/commands/aspirate.py +103 -53
  87. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  88. opentrons/protocol_engine/commands/blow_out.py +44 -39
  89. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  90. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  91. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  92. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  93. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  94. opentrons/protocol_engine/commands/command.py +73 -66
  95. opentrons/protocol_engine/commands/command_unions.py +140 -1
  96. opentrons/protocol_engine/commands/comment.py +1 -1
  97. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  98. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  99. opentrons/protocol_engine/commands/custom.py +6 -12
  100. opentrons/protocol_engine/commands/dispense.py +82 -48
  101. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  102. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  103. opentrons/protocol_engine/commands/drop_tip_in_place.py +79 -8
  104. opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
  105. opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
  106. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
  107. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  108. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  109. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  112. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  113. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  114. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  115. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  116. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  117. opentrons/protocol_engine/commands/home.py +13 -4
  118. opentrons/protocol_engine/commands/liquid_probe.py +125 -31
  119. opentrons/protocol_engine/commands/load_labware.py +33 -6
  120. opentrons/protocol_engine/commands/load_lid.py +146 -0
  121. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  122. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  123. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  124. opentrons/protocol_engine/commands/load_module.py +31 -10
  125. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  126. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  127. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  128. opentrons/protocol_engine/commands/move_labware.py +28 -6
  129. opentrons/protocol_engine/commands/move_relative.py +35 -25
  130. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  131. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  132. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  133. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  134. opentrons/protocol_engine/commands/movement_common.py +338 -0
  135. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  136. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  137. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  138. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  139. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  140. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  141. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  142. opentrons/protocol_engine/commands/robot/common.py +18 -0
  143. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  144. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  145. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  146. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  147. opentrons/protocol_engine/commands/save_position.py +14 -5
  148. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  149. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  150. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  151. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  152. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  153. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  154. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +9 -3
  158. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  159. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  160. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  161. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  162. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  163. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  164. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +5 -2
  165. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +13 -4
  166. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +2 -5
  167. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
  168. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +4 -2
  169. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +2 -5
  170. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  171. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  172. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  173. opentrons/protocol_engine/errors/__init__.py +12 -0
  174. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  175. opentrons/protocol_engine/errors/exceptions.py +76 -0
  176. opentrons/protocol_engine/execution/command_executor.py +1 -1
  177. opentrons/protocol_engine/execution/equipment.py +73 -5
  178. opentrons/protocol_engine/execution/gantry_mover.py +369 -8
  179. opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
  180. opentrons/protocol_engine/execution/movement.py +27 -0
  181. opentrons/protocol_engine/execution/pipetting.py +5 -1
  182. opentrons/protocol_engine/execution/tip_handler.py +34 -15
  183. opentrons/protocol_engine/notes/notes.py +1 -1
  184. opentrons/protocol_engine/protocol_engine.py +7 -6
  185. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  186. opentrons/protocol_engine/resources/labware_validation.py +18 -0
  187. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  188. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  189. opentrons/protocol_engine/slot_standardization.py +9 -9
  190. opentrons/protocol_engine/state/_move_types.py +9 -5
  191. opentrons/protocol_engine/state/_well_math.py +193 -0
  192. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  193. opentrons/protocol_engine/state/command_history.py +12 -0
  194. opentrons/protocol_engine/state/commands.py +22 -14
  195. opentrons/protocol_engine/state/files.py +10 -12
  196. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  197. opentrons/protocol_engine/state/frustum_helpers.py +63 -69
  198. opentrons/protocol_engine/state/geometry.py +47 -1
  199. opentrons/protocol_engine/state/labware.py +92 -26
  200. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  201. opentrons/protocol_engine/state/liquids.py +16 -4
  202. opentrons/protocol_engine/state/modules.py +56 -71
  203. opentrons/protocol_engine/state/motion.py +6 -1
  204. opentrons/protocol_engine/state/pipettes.py +149 -58
  205. opentrons/protocol_engine/state/state.py +21 -2
  206. opentrons/protocol_engine/state/state_summary.py +4 -2
  207. opentrons/protocol_engine/state/tips.py +11 -44
  208. opentrons/protocol_engine/state/update_types.py +343 -48
  209. opentrons/protocol_engine/state/wells.py +19 -11
  210. opentrons/protocol_engine/types.py +176 -28
  211. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  212. opentrons/protocol_reader/file_format_validator.py +5 -5
  213. opentrons/protocol_runner/json_file_reader.py +9 -3
  214. opentrons/protocol_runner/json_translator.py +51 -25
  215. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  216. opentrons/protocol_runner/protocol_runner.py +35 -4
  217. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  218. opentrons/protocol_runner/run_orchestrator.py +13 -3
  219. opentrons/protocols/advanced_control/common.py +38 -0
  220. opentrons/protocols/advanced_control/mix.py +1 -1
  221. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  222. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  223. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  224. opentrons/protocols/api_support/definitions.py +1 -1
  225. opentrons/protocols/api_support/instrument.py +1 -1
  226. opentrons/protocols/api_support/util.py +10 -0
  227. opentrons/protocols/labware.py +70 -8
  228. opentrons/protocols/models/json_protocol.py +5 -9
  229. opentrons/simulate.py +3 -1
  230. opentrons/types.py +162 -2
  231. opentrons/util/entrypoint_util.py +2 -5
  232. opentrons/util/logging_config.py +1 -1
  233. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
  234. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
  235. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
  236. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
  237. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,6 @@
1
1
  """Utilities for calculating motion correctly."""
2
+ from logging import getLogger
3
+
2
4
  from functools import lru_cache
3
5
  from typing import Callable, Dict, Union, Optional, cast
4
6
  from collections import OrderedDict
@@ -11,6 +13,7 @@ from opentrons.util import linal
11
13
 
12
14
  from .types import Axis, OT3Mount
13
15
 
16
+ log = getLogger(__name__)
14
17
 
15
18
  # TODO: The offset_for_mount function should be defined with an overload
16
19
  # set, as with other functions in this module. Unfortunately, mypy < 0.920
@@ -36,6 +39,19 @@ from .types import Axis, OT3Mount
36
39
  # ) -> Point:
37
40
  # ...
38
41
 
42
+ EMPTY_ORDERED_DICT = OrderedDict(
43
+ (
44
+ (Axis.X, 0.0),
45
+ (Axis.Y, 0.0),
46
+ (Axis.Z_L, 0.0),
47
+ (Axis.Z_R, 0.0),
48
+ (Axis.Z_G, 0.0),
49
+ (Axis.P_L, 0.0),
50
+ (Axis.P_R, 0.0),
51
+ (Axis.Q, 0.0),
52
+ )
53
+ )
54
+
39
55
 
40
56
  @lru_cache(4)
41
57
  def offset_for_mount(
@@ -68,6 +84,7 @@ def target_position_from_absolute(
68
84
  )
69
85
  primary_cp = get_critical_point(mount)
70
86
  primary_z = Axis.by_mount(mount)
87
+
71
88
  target_position = OrderedDict(
72
89
  (
73
90
  (Axis.X, abs_position.x - offset.x - primary_cp.x),
@@ -97,6 +114,57 @@ def target_position_from_relative(
97
114
  return target_position
98
115
 
99
116
 
117
+ def target_axis_map_from_absolute(
118
+ primary_mount: Union[OT3Mount, Mount],
119
+ axis_map: Dict[Axis, float],
120
+ get_critical_point: Callable[[Union[Mount, OT3Mount]], Point],
121
+ left_mount_offset: Point,
122
+ right_mount_offset: Point,
123
+ gripper_mount_offset: Optional[Point] = None,
124
+ ) -> "OrderedDict[Axis, float]":
125
+ """Create an absolute target position for all specified machine axes."""
126
+ keys_for_target_position = list(axis_map.keys())
127
+
128
+ offset = offset_for_mount(
129
+ primary_mount, left_mount_offset, right_mount_offset, gripper_mount_offset
130
+ )
131
+ primary_cp = get_critical_point(primary_mount)
132
+ primary_z = Axis.by_mount(primary_mount)
133
+ target_position = OrderedDict()
134
+
135
+ if Axis.X in keys_for_target_position:
136
+ target_position[Axis.X] = axis_map[Axis.X] - offset.x - primary_cp.x
137
+ if Axis.Y in keys_for_target_position:
138
+ target_position[Axis.Y] = axis_map[Axis.Y] - offset.y - primary_cp.y
139
+ if primary_z in keys_for_target_position:
140
+ # Since this function is intended to be used in conjunction with `API.move_axes`
141
+ # we must leave out the carriage offset subtraction from the target position as
142
+ # `move_axes` already does this calculation.
143
+ target_position[primary_z] = axis_map[primary_z] - primary_cp.z
144
+
145
+ target_position.update(
146
+ {ax: val for ax, val in axis_map.items() if ax not in Axis.gantry_axes()}
147
+ )
148
+ return target_position
149
+
150
+
151
+ def target_axis_map_from_relative(
152
+ axis_map: Dict[Axis, float],
153
+ current_position: Dict[Axis, float],
154
+ ) -> "OrderedDict[Axis, float]":
155
+ """Create a target position for all specified machine axes."""
156
+ target_position = OrderedDict(
157
+ (
158
+ (ax, current_position[ax] + axis_map[ax])
159
+ for ax in EMPTY_ORDERED_DICT.keys()
160
+ if ax in axis_map.keys()
161
+ )
162
+ )
163
+ log.info(f"Current position {current_position} and axis map delta {axis_map}")
164
+ log.info(f"Relative move target {target_position}")
165
+ return target_position
166
+
167
+
100
168
  def target_position_from_plunger(
101
169
  mount: Union[Mount, OT3Mount],
102
170
  delta: float,
@@ -1,11 +1,13 @@
1
1
  from typing import Dict, List, Optional, Any, Sequence, Iterator, Tuple, cast
2
2
  from dataclasses import dataclass
3
3
  from collections import OrderedDict
4
- from enum import Enum
5
4
  from itertools import chain
6
5
 
7
6
  from opentrons.hardware_control.types import CriticalPoint
8
- from opentrons.types import Point
7
+ from opentrons.types import (
8
+ Point,
9
+ NozzleConfigurationType,
10
+ )
9
11
  from opentrons_shared_data.pipette.pipette_definition import (
10
12
  PipetteGeometryDefinition,
11
13
  PipetteRowDefinition,
@@ -41,43 +43,6 @@ def _row_col_indices_for_nozzle(
41
43
  )
42
44
 
43
45
 
44
- class NozzleConfigurationType(Enum):
45
- """
46
- Nozzle Configuration Type.
47
-
48
- Represents the current nozzle
49
- configuration stored in NozzleMap
50
- """
51
-
52
- COLUMN = "COLUMN"
53
- ROW = "ROW"
54
- SINGLE = "SINGLE"
55
- FULL = "FULL"
56
- SUBRECT = "SUBRECT"
57
-
58
- @classmethod
59
- def determine_nozzle_configuration(
60
- cls,
61
- physical_rows: "OrderedDict[str, List[str]]",
62
- current_rows: "OrderedDict[str, List[str]]",
63
- physical_cols: "OrderedDict[str, List[str]]",
64
- current_cols: "OrderedDict[str, List[str]]",
65
- ) -> "NozzleConfigurationType":
66
- """
67
- Determine the nozzle configuration based on the starting and
68
- ending nozzle.
69
- """
70
- if physical_rows == current_rows and physical_cols == current_cols:
71
- return NozzleConfigurationType.FULL
72
- if len(current_rows) == 1 and len(current_cols) == 1:
73
- return NozzleConfigurationType.SINGLE
74
- if len(current_rows) == 1:
75
- return NozzleConfigurationType.ROW
76
- if len(current_cols) == 1:
77
- return NozzleConfigurationType.COLUMN
78
- return NozzleConfigurationType.SUBRECT
79
-
80
-
81
46
  @dataclass
82
47
  class NozzleMap:
83
48
  """
@@ -113,6 +78,28 @@ class NozzleMap:
113
78
  full_instrument_rows: Dict[str, List[str]]
114
79
  #: A map of all the rows of an instrument
115
80
 
81
+ @classmethod
82
+ def determine_nozzle_configuration(
83
+ cls,
84
+ physical_rows: "OrderedDict[str, List[str]]",
85
+ current_rows: "OrderedDict[str, List[str]]",
86
+ physical_cols: "OrderedDict[str, List[str]]",
87
+ current_cols: "OrderedDict[str, List[str]]",
88
+ ) -> "NozzleConfigurationType":
89
+ """
90
+ Determine the nozzle configuration based on the starting and
91
+ ending nozzle.
92
+ """
93
+ if physical_rows == current_rows and physical_cols == current_cols:
94
+ return NozzleConfigurationType.FULL
95
+ if len(current_rows) == 1 and len(current_cols) == 1:
96
+ return NozzleConfigurationType.SINGLE
97
+ if len(current_rows) == 1:
98
+ return NozzleConfigurationType.ROW
99
+ if len(current_cols) == 1:
100
+ return NozzleConfigurationType.COLUMN
101
+ return NozzleConfigurationType.SUBRECT
102
+
116
103
  def __str__(self) -> str:
117
104
  return f"back_left_nozzle: {self.back_left} front_right_nozzle: {self.front_right} configuration: {self.configuration}"
118
105
 
@@ -216,6 +203,16 @@ class NozzleMap:
216
203
  """The total number of active nozzles in the configuration, and thus the number of tips that will be picked up."""
217
204
  return len(self.map_store)
218
205
 
206
+ @property
207
+ def physical_nozzle_count(self) -> int:
208
+ """The number of physical nozzles, regardless of configuration."""
209
+ return len(self.full_instrument_map_store)
210
+
211
+ @property
212
+ def active_nozzles(self) -> list[str]:
213
+ """An unstructured list of all nozzles active in the configuration."""
214
+ return list(self.map_store.keys())
215
+
219
216
  @classmethod
220
217
  def build( # noqa: C901
221
218
  cls,
@@ -274,7 +271,7 @@ class NozzleMap:
274
271
  )
275
272
 
276
273
  if (
277
- NozzleConfigurationType.determine_nozzle_configuration(
274
+ cls.determine_nozzle_configuration(
278
275
  physical_rows, rows, physical_columns, columns
279
276
  )
280
277
  != NozzleConfigurationType.FULL
@@ -289,6 +286,7 @@ class NozzleMap:
289
286
  if valid_nozzle_maps.maps[map_key] == list(map_store.keys()):
290
287
  validated_map_key = map_key
291
288
  break
289
+
292
290
  if validated_map_key is None:
293
291
  raise IncompatibleNozzleConfiguration(
294
292
  "Attempted Nozzle Configuration does not match any approved map layout for the current pipette."
@@ -302,7 +300,7 @@ class NozzleMap:
302
300
  full_instrument_map_store=physical_nozzles,
303
301
  full_instrument_rows=physical_rows,
304
302
  columns=columns,
305
- configuration=NozzleConfigurationType.determine_nozzle_configuration(
303
+ configuration=cls.determine_nozzle_configuration(
306
304
  physical_rows, rows, physical_columns, columns
307
305
  ),
308
306
  )
@@ -968,7 +968,7 @@ def load_attitude_matrix(to_default: bool = True) -> DeckCalibration:
968
968
  return DeckCalibration(
969
969
  attitude=apply_machine_transform(calibration_data.attitude),
970
970
  source=calibration_data.source,
971
- status=types.CalibrationStatus(**calibration_data.status.dict()),
971
+ status=types.CalibrationStatus(**calibration_data.status.model_dump()),
972
972
  belt_attitude=calibration_data.attitude,
973
973
  last_modified=calibration_data.lastModified,
974
974
  pipette_calibrated_with=calibration_data.pipetteCalibratedWith,
@@ -32,6 +32,7 @@ from opentrons_shared_data.pipette.types import (
32
32
  )
33
33
  from opentrons_shared_data.pipette import (
34
34
  pipette_load_name_conversions as pipette_load_name,
35
+ pipette_definition,
35
36
  )
36
37
  from opentrons_shared_data.robot.types import RobotType
37
38
 
@@ -45,7 +46,6 @@ from opentrons.config.types import (
45
46
  LiquidProbeSettings,
46
47
  )
47
48
  from opentrons.drivers.rpi_drivers.types import USBPort, PortGroup
48
- from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType
49
49
  from opentrons_shared_data.errors.exceptions import (
50
50
  EnumeratedError,
51
51
  PythonException,
@@ -98,6 +98,7 @@ from .types import (
98
98
  EstopState,
99
99
  HardwareFeatureFlags,
100
100
  FailedTipStateCheck,
101
+ PipetteSensorResponseQueue,
101
102
  )
102
103
  from .errors import (
103
104
  UpdateOngoingError,
@@ -143,8 +144,6 @@ from .backends.types import HWStopCondition
143
144
  from .backends.flex_protocol import FlexBackend
144
145
  from .backends.ot3simulator import OT3Simulator
145
146
  from .backends.errors import SubsystemUpdating
146
- from opentrons_hardware.firmware_bindings.constants import SensorId
147
- from opentrons_hardware.sensors.types import SensorDataType
148
147
 
149
148
  mod_log = logging.getLogger(__name__)
150
149
 
@@ -299,8 +298,11 @@ class OT3API(
299
298
  async def set_system_constraints_for_plunger_acceleration(
300
299
  self, mount: OT3Mount, acceleration: float
301
300
  ) -> None:
301
+ high_speed_pipette = self._pipette_handler.get_pipette(
302
+ mount
303
+ ).is_high_speed_pipette()
302
304
  self._backend.update_constraints_for_plunger_acceleration(
303
- mount, acceleration, self._gantry_load
305
+ mount, acceleration, self._gantry_load, high_speed_pipette
304
306
  )
305
307
 
306
308
  @contextlib.asynccontextmanager
@@ -353,7 +355,9 @@ class OT3API(
353
355
  def _reset_last_mount(self) -> None:
354
356
  self._last_moved_mount = None
355
357
 
356
- def _deck_from_machine(self, machine_pos: Dict[Axis, float]) -> Dict[Axis, float]:
358
+ def get_deck_from_machine(
359
+ self, machine_pos: Dict[Axis, float]
360
+ ) -> Dict[Axis, float]:
357
361
  return deck_from_machine(
358
362
  machine_pos=machine_pos,
359
363
  attitude=self._robot_calibration.deck_calibration.attitude,
@@ -633,10 +637,31 @@ class OT3API(
633
637
  self._feature_flags.use_old_aspiration_functions,
634
638
  )
635
639
  self._pipette_handler.hardware_instruments[mount] = p
640
+
641
+ if config is not None:
642
+ self._set_pressure_sensor_available(mount, instrument_config=config)
643
+
636
644
  # TODO (lc 12-5-2022) Properly support backwards compatibility
637
645
  # when applicable
638
646
  return skipped
639
647
 
648
+ def get_pressure_sensor_available(self, mount: OT3Mount) -> bool:
649
+ pip_axis = Axis.of_main_tool_actuator(mount)
650
+ return self._backend.get_pressure_sensor_available(pip_axis)
651
+
652
+ def _set_pressure_sensor_available(
653
+ self,
654
+ mount: OT3Mount,
655
+ instrument_config: pipette_definition.PipetteConfigurations,
656
+ ) -> None:
657
+ pressure_sensor_available = (
658
+ "pressure" in instrument_config.available_sensors.sensors
659
+ )
660
+ pip_axis = Axis.of_main_tool_actuator(mount)
661
+ self._backend.set_pressure_sensor_available(
662
+ pipette_axis=pip_axis, available=pressure_sensor_available
663
+ )
664
+
640
665
  async def cache_gripper(self, instrument_data: AttachedGripper) -> bool:
641
666
  """Set up gripper based on scanned information."""
642
667
  grip_cal = load_gripper_calibration_offset(instrument_data.get("id"))
@@ -775,6 +800,8 @@ class OT3API(
775
800
  """
776
801
  Function to update motor estimation for a set of axes
777
802
  """
803
+ await self._backend.update_motor_status()
804
+
778
805
  if axes is None:
779
806
  axes = [ax for ax in Axis]
780
807
 
@@ -943,8 +970,8 @@ class OT3API(
943
970
  ):
944
971
  # move toward home until a safe distance
945
972
  await self._backend.tip_action(
946
- origin={Axis.Q: current_pos_float},
947
- targets=[({Axis.Q: self._config.safe_home_distance}, 400)],
973
+ origin=current_pos_float,
974
+ targets=[(self._config.safe_home_distance, 400)],
948
975
  )
949
976
 
950
977
  # update current position
@@ -1021,14 +1048,14 @@ class OT3API(
1021
1048
 
1022
1049
  async def _cache_current_position(self) -> Dict[Axis, float]:
1023
1050
  """Cache current position from backend and return in absolute deck coords."""
1024
- self._current_position = self._deck_from_machine(
1051
+ self._current_position = self.get_deck_from_machine(
1025
1052
  await self._backend.update_position()
1026
1053
  )
1027
1054
  return self._current_position
1028
1055
 
1029
1056
  async def _cache_encoder_position(self) -> Dict[Axis, float]:
1030
1057
  """Cache encoder position from backend and return in absolute deck coords."""
1031
- self._encoder_position = self._deck_from_machine(
1058
+ self._encoder_position = self.get_deck_from_machine(
1032
1059
  await self._backend.update_encoder_position()
1033
1060
  )
1034
1061
  if self.has_gripper():
@@ -1162,7 +1189,7 @@ class OT3API(
1162
1189
  speed: Optional[float] = None,
1163
1190
  critical_point: Optional[CriticalPoint] = None,
1164
1191
  max_speeds: Union[None, Dict[Axis, float], OT3AxisMap[float]] = None,
1165
- _expect_stalls: bool = False,
1192
+ expect_stalls: bool = False,
1166
1193
  ) -> None:
1167
1194
  """Move the critical point of the specified mount to a location
1168
1195
  relative to the deck, at the specified speed."""
@@ -1206,7 +1233,7 @@ class OT3API(
1206
1233
  target_position,
1207
1234
  speed=speed,
1208
1235
  max_speeds=checked_max,
1209
- expect_stalls=_expect_stalls,
1236
+ expect_stalls=expect_stalls,
1210
1237
  )
1211
1238
 
1212
1239
  async def move_axes( # noqa: C901
@@ -1214,6 +1241,7 @@ class OT3API(
1214
1241
  position: Mapping[Axis, float],
1215
1242
  speed: Optional[float] = None,
1216
1243
  max_speeds: Optional[Dict[Axis, float]] = None,
1244
+ expect_stalls: bool = False,
1217
1245
  ) -> None:
1218
1246
  """Moves the effectors of the specified axis to the specified position.
1219
1247
  The effector of the x,y axis is the center of the carriage.
@@ -1228,7 +1256,9 @@ class OT3API(
1228
1256
  message=f"{axis} is not present", detail={"axis": str(axis)}
1229
1257
  )
1230
1258
 
1259
+ self._log.info(f"Attempting to move {position} with speed {speed}.")
1231
1260
  if not self._backend.check_encoder_status(list(position.keys())):
1261
+ self._log.info("Calling home in move_axes")
1232
1262
  await self.home()
1233
1263
  self._assert_motor_ok(list(position.keys()))
1234
1264
 
@@ -1267,7 +1297,11 @@ class OT3API(
1267
1297
  if axis not in absolute_positions:
1268
1298
  absolute_positions[axis] = position_value
1269
1299
 
1270
- await self._move(target_position=absolute_positions, speed=speed)
1300
+ await self._move(
1301
+ target_position=absolute_positions,
1302
+ speed=speed,
1303
+ expect_stalls=expect_stalls,
1304
+ )
1271
1305
 
1272
1306
  async def move_rel(
1273
1307
  self,
@@ -1277,7 +1311,7 @@ class OT3API(
1277
1311
  max_speeds: Union[None, Dict[Axis, float], OT3AxisMap[float]] = None,
1278
1312
  check_bounds: MotionChecks = MotionChecks.NONE,
1279
1313
  fail_on_not_homed: bool = False,
1280
- _expect_stalls: bool = False,
1314
+ expect_stalls: bool = False,
1281
1315
  ) -> None:
1282
1316
  """Move the critical point of the specified mount by a specified
1283
1317
  displacement in a specified direction, at the specified speed."""
@@ -1319,7 +1353,7 @@ class OT3API(
1319
1353
  speed=speed,
1320
1354
  max_speeds=checked_max,
1321
1355
  check_bounds=check_bounds,
1322
- expect_stalls=_expect_stalls,
1356
+ expect_stalls=expect_stalls,
1323
1357
  )
1324
1358
 
1325
1359
  async def _cache_and_maybe_retract_mount(self, mount: OT3Mount) -> None:
@@ -1439,6 +1473,10 @@ class OT3API(
1439
1473
  check_motion_bounds(to_check, target_position, bounds, check_bounds)
1440
1474
  self._log.info(f"Move: deck {target_position} becomes machine {machine_pos}")
1441
1475
  origin = await self._backend.update_position()
1476
+
1477
+ if self._gantry_load == GantryLoad.HIGH_THROUGHPUT:
1478
+ origin[Axis.Q] = self._backend.gear_motor_position or 0.0
1479
+
1442
1480
  async with contextlib.AsyncExitStack() as stack:
1443
1481
  if acquire_lock:
1444
1482
  await stack.enter_async_context(self._motion_lock)
@@ -1640,7 +1678,12 @@ class OT3API(
1640
1678
  await self._backend.disengage_axes(which)
1641
1679
 
1642
1680
  async def engage_axes(self, which: List[Axis]) -> None:
1643
- await self._backend.engage_axes(which)
1681
+ await self._backend.engage_axes(
1682
+ [axis for axis in which if self._backend.axis_is_present(axis)]
1683
+ )
1684
+
1685
+ def axis_is_present(self, axis: Axis) -> bool:
1686
+ return self._backend.axis_is_present(axis)
1644
1687
 
1645
1688
  async def get_limit_switches(self) -> Dict[Axis, bool]:
1646
1689
  res = await self._backend.get_limit_switches()
@@ -1826,7 +1869,7 @@ class OT3API(
1826
1869
  if (
1827
1870
  self.gantry_load == GantryLoad.HIGH_THROUGHPUT
1828
1871
  and instrument.nozzle_manager.current_configuration.configuration
1829
- == NozzleConfigurationType.FULL
1872
+ == top_types.NozzleConfigurationType.FULL
1830
1873
  ):
1831
1874
  spec = self._pipette_handler.plan_ht_pick_up_tip(
1832
1875
  instrument.nozzle_manager.current_configuration.tip_count
@@ -2156,8 +2199,8 @@ class OT3API(
2156
2199
  # only move tip motors if they are not already below the sensor
2157
2200
  if tip_motor_pos_float < tip_presence_check_target:
2158
2201
  await self._backend.tip_action(
2159
- origin={Axis.Q: tip_motor_pos_float},
2160
- targets=[({Axis.Q: tip_presence_check_target}, 400)],
2202
+ origin=tip_motor_pos_float,
2203
+ targets=[(tip_presence_check_target, 400)],
2161
2204
  )
2162
2205
  try:
2163
2206
  yield
@@ -2228,11 +2271,11 @@ class OT3API(
2228
2271
  gear_origin_float = self._backend.gear_motor_position or 0.0
2229
2272
 
2230
2273
  move_targets = [
2231
- ({Axis.Q: move_segment.distance}, move_segment.speed or 400)
2274
+ (move_segment.distance, move_segment.speed or 400)
2232
2275
  for move_segment in pipette_spec
2233
2276
  ]
2234
2277
  await self._backend.tip_action(
2235
- origin={Axis.Q: gear_origin_float}, targets=move_targets
2278
+ origin=gear_origin_float, targets=move_targets
2236
2279
  )
2237
2280
  await self.home_gear_motors()
2238
2281
 
@@ -2282,11 +2325,16 @@ class OT3API(
2282
2325
  instrument.working_volume = tip_volume
2283
2326
 
2284
2327
  async def tip_drop_moves(
2285
- self, mount: Union[top_types.Mount, OT3Mount], home_after: bool = False
2328
+ self,
2329
+ mount: Union[top_types.Mount, OT3Mount],
2330
+ home_after: bool = False,
2331
+ ignore_plunger: bool = False,
2286
2332
  ) -> None:
2287
2333
  realmount = OT3Mount.from_mount(mount)
2288
-
2289
- await self._move_to_plunger_bottom(realmount, rate=1.0, check_current_vol=False)
2334
+ if ignore_plunger is False:
2335
+ await self._move_to_plunger_bottom(
2336
+ realmount, rate=1.0, check_current_vol=False
2337
+ )
2290
2338
 
2291
2339
  if self.gantry_load == GantryLoad.HIGH_THROUGHPUT:
2292
2340
  spec = self._pipette_handler.plan_ht_drop_tip()
@@ -2557,7 +2605,7 @@ class OT3API(
2557
2605
  mount: Union[top_types.Mount, OT3Mount],
2558
2606
  critical_point: Optional[CriticalPoint] = None,
2559
2607
  ) -> float:
2560
- carriage_pos = self._deck_from_machine(self._backend.home_position())
2608
+ carriage_pos = self.get_deck_from_machine(self._backend.home_position())
2561
2609
  pos_at_home = self._effector_pos_from_carriage_pos(
2562
2610
  OT3Mount.from_mount(mount), carriage_pos, critical_point
2563
2611
  )
@@ -2639,10 +2687,9 @@ class OT3API(
2639
2687
  probe_settings: LiquidProbeSettings,
2640
2688
  probe: InstrumentProbeType,
2641
2689
  p_travel: float,
2690
+ z_offset_for_plunger_prep: float,
2642
2691
  force_both_sensors: bool = False,
2643
- response_queue: Optional[
2644
- asyncio.Queue[Dict[SensorId, List[SensorDataType]]]
2645
- ] = None,
2692
+ response_queue: Optional[PipetteSensorResponseQueue] = None,
2646
2693
  ) -> float:
2647
2694
  plunger_direction = -1 if probe_settings.aspirate_while_sensing else 1
2648
2695
  end_z = await self._backend.liquid_probe(
@@ -2653,13 +2700,14 @@ class OT3API(
2653
2700
  probe_settings.sensor_threshold_pascals,
2654
2701
  probe_settings.plunger_impulse_time,
2655
2702
  probe_settings.samples_for_baselining,
2703
+ z_offset_for_plunger_prep,
2656
2704
  probe=probe,
2657
2705
  force_both_sensors=force_both_sensors,
2658
2706
  response_queue=response_queue,
2659
2707
  )
2660
2708
  machine_pos = await self._backend.update_position()
2661
2709
  machine_pos[Axis.by_mount(mount)] = end_z
2662
- deck_end_z = self._deck_from_machine(machine_pos)[Axis.by_mount(mount)]
2710
+ deck_end_z = self.get_deck_from_machine(machine_pos)[Axis.by_mount(mount)]
2663
2711
  offset = offset_for_mount(
2664
2712
  mount,
2665
2713
  top_types.Point(*self._config.left_mount_offset),
@@ -2676,9 +2724,7 @@ class OT3API(
2676
2724
  probe_settings: Optional[LiquidProbeSettings] = None,
2677
2725
  probe: Optional[InstrumentProbeType] = None,
2678
2726
  force_both_sensors: bool = False,
2679
- response_queue: Optional[
2680
- asyncio.Queue[Dict[SensorId, List[SensorDataType]]]
2681
- ] = None,
2727
+ response_queue: Optional[PipetteSensorResponseQueue] = None,
2682
2728
  ) -> float:
2683
2729
  """Search for and return liquid level height.
2684
2730
 
@@ -2804,6 +2850,7 @@ class OT3API(
2804
2850
  probe_settings,
2805
2851
  checked_probe,
2806
2852
  plunger_travel_mm + sensor_baseline_plunger_move_mm,
2853
+ z_offset_for_plunger_prep,
2807
2854
  force_both_sensors,
2808
2855
  response_queue,
2809
2856
  )
@@ -14,6 +14,9 @@ class GripperController(Protocol):
14
14
  ) -> None:
15
15
  ...
16
16
 
17
+ async def home_gripper_jaw(self) -> None:
18
+ ...
19
+
17
20
  async def ungrip(self, force_newtons: Optional[float] = None) -> None:
18
21
  """Release gripped object.
19
22
 
@@ -1,7 +1,7 @@
1
1
  from typing import Dict, Optional
2
2
  from typing_extensions import Protocol
3
3
 
4
- from ..types import SubSystem, SubSystemState
4
+ from ..types import SubSystem, SubSystemState, Axis
5
5
 
6
6
 
7
7
  class HardwareManager(Protocol):
@@ -45,3 +45,7 @@ class HardwareManager(Protocol):
45
45
  async def get_serial_number(self) -> Optional[str]:
46
46
  """Get the robot serial number, if provisioned. If not provisioned, will be None."""
47
47
  ...
48
+
49
+ def axis_is_present(self, axis: Axis) -> bool:
50
+ """Get whether a motor axis is present on the machine."""
51
+ ...
@@ -1,6 +1,8 @@
1
1
  from typing import Optional
2
2
  from typing_extensions import Protocol
3
3
 
4
+ from opentrons.types import Point
5
+ from opentrons.hardware_control.types import CriticalPoint
4
6
  from .types import MountArgType, CalibrationType, ConfigType
5
7
 
6
8
  from .instrument_configurer import InstrumentConfigurer
@@ -16,6 +18,22 @@ class LiquidHandler(
16
18
  Calibratable[CalibrationType],
17
19
  Protocol[CalibrationType, MountArgType, ConfigType],
18
20
  ):
21
+ def critical_point_for(
22
+ self,
23
+ mount: MountArgType,
24
+ cp_override: Optional[CriticalPoint] = None,
25
+ ) -> Point:
26
+ """
27
+ Determine the current critical point for the specified mount.
28
+
29
+ :param mount: A robot mount that the instrument is on.
30
+ :param cp_override: The critical point override to use.
31
+
32
+ If no critical point override is specified, the robot defaults to nozzle location `A1` or the mount critical point.
33
+ :return: Point.
34
+ """
35
+ ...
36
+
19
37
  async def update_nozzle_configuration_for_mount(
20
38
  self,
21
39
  mount: MountArgType,
@@ -165,7 +183,10 @@ class LiquidHandler(
165
183
  ...
166
184
 
167
185
  async def tip_drop_moves(
168
- self, mount: MountArgType, home_after: bool = True
186
+ self,
187
+ mount: MountArgType,
188
+ home_after: bool = True,
189
+ ignore_plunger: bool = False,
169
190
  ) -> None:
170
191
  ...
171
192
 
@@ -9,6 +9,12 @@ from .types import MountArgType
9
9
  class MotionController(Protocol[MountArgType]):
10
10
  """Protocol specifying fundamental motion controls."""
11
11
 
12
+ def get_deck_from_machine(
13
+ self, machine_pos: Dict[Axis, float]
14
+ ) -> Dict[Axis, float]:
15
+ """Convert machine coordinates to deck coordinates."""
16
+ ...
17
+
12
18
  async def halt(self, disengage_before_stopping: bool = False) -> None:
13
19
  """Immediately stop motion.
14
20
 
@@ -165,6 +171,7 @@ class MotionController(Protocol[MountArgType]):
165
171
  position: Mapping[Axis, float],
166
172
  speed: Optional[float] = None,
167
173
  max_speeds: Optional[Dict[Axis, float]] = None,
174
+ expect_stalls: bool = False,
168
175
  ) -> None:
169
176
  """Moves the effectors of the specified axis to the specified position.
170
177
  The effector of the x,y axis is the center of the carriage.
@@ -154,7 +154,7 @@ def load_attitude_matrix() -> DeckCalibration:
154
154
  return DeckCalibration(
155
155
  attitude=calibration_data.attitude,
156
156
  source=calibration_data.source,
157
- status=types.CalibrationStatus(**calibration_data.status.dict()),
157
+ status=types.CalibrationStatus(**calibration_data.status.model_dump()),
158
158
  last_modified=calibration_data.last_modified,
159
159
  pipette_calibrated_with=calibration_data.pipette_calibrated_with,
160
160
  tiprack=calibration_data.tiprack,