opentrons 8.2.0a4__py2.py3-none-any.whl → 8.3.0a1__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 (229) 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 +28 -20
  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 +60 -23
  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 +18 -0
  47. opentrons/hardware_control/protocols/motion_controller.py +6 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/protocol_api/__init__.py +20 -1
  51. opentrons/protocol_api/_liquid.py +24 -49
  52. opentrons/protocol_api/_liquid_properties.py +754 -0
  53. opentrons/protocol_api/_types.py +24 -0
  54. opentrons/protocol_api/core/common.py +2 -0
  55. opentrons/protocol_api/core/engine/instrument.py +82 -10
  56. opentrons/protocol_api/core/engine/labware.py +29 -7
  57. opentrons/protocol_api/core/engine/protocol.py +130 -5
  58. opentrons/protocol_api/core/engine/robot.py +139 -0
  59. opentrons/protocol_api/core/engine/well.py +4 -1
  60. opentrons/protocol_api/core/instrument.py +46 -4
  61. opentrons/protocol_api/core/labware.py +13 -4
  62. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +37 -3
  63. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  65. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  66. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +37 -3
  67. opentrons/protocol_api/core/protocol.py +34 -1
  68. opentrons/protocol_api/core/robot.py +51 -0
  69. opentrons/protocol_api/instrument_context.py +158 -44
  70. opentrons/protocol_api/labware.py +231 -7
  71. opentrons/protocol_api/module_contexts.py +21 -17
  72. opentrons/protocol_api/protocol_context.py +125 -4
  73. opentrons/protocol_api/robot_context.py +204 -32
  74. opentrons/protocol_api/validation.py +262 -3
  75. opentrons/protocol_engine/__init__.py +4 -0
  76. opentrons/protocol_engine/actions/actions.py +2 -3
  77. opentrons/protocol_engine/clients/sync_client.py +18 -0
  78. opentrons/protocol_engine/commands/__init__.py +81 -0
  79. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
  80. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
  81. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
  82. opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
  83. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  84. opentrons/protocol_engine/commands/aspirate.py +103 -53
  85. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  86. opentrons/protocol_engine/commands/blow_out.py +44 -39
  87. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  88. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  89. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  90. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  91. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  92. opentrons/protocol_engine/commands/command.py +73 -66
  93. opentrons/protocol_engine/commands/command_unions.py +101 -1
  94. opentrons/protocol_engine/commands/comment.py +1 -1
  95. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  96. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  97. opentrons/protocol_engine/commands/custom.py +6 -12
  98. opentrons/protocol_engine/commands/dispense.py +82 -48
  99. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  100. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  101. opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
  102. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  103. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  104. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  105. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  106. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  107. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  108. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  109. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  112. opentrons/protocol_engine/commands/home.py +13 -4
  113. opentrons/protocol_engine/commands/liquid_probe.py +67 -24
  114. opentrons/protocol_engine/commands/load_labware.py +29 -7
  115. opentrons/protocol_engine/commands/load_lid.py +146 -0
  116. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  117. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  118. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  119. opentrons/protocol_engine/commands/load_module.py +31 -10
  120. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  121. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  122. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  123. opentrons/protocol_engine/commands/move_labware.py +19 -6
  124. opentrons/protocol_engine/commands/move_relative.py +35 -25
  125. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  126. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  127. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  128. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  129. opentrons/protocol_engine/commands/movement_common.py +338 -0
  130. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  131. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  132. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  133. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  134. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  135. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  136. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  137. opentrons/protocol_engine/commands/robot/common.py +18 -0
  138. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  139. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  140. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  141. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  142. opentrons/protocol_engine/commands/save_position.py +14 -5
  143. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  144. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  145. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  146. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  147. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  148. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  149. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  150. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  151. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  152. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
  153. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  154. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  155. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  158. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  159. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
  160. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
  161. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
  162. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
  163. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  164. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  165. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  166. opentrons/protocol_engine/errors/__init__.py +8 -0
  167. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  168. opentrons/protocol_engine/errors/exceptions.py +50 -0
  169. opentrons/protocol_engine/execution/command_executor.py +1 -1
  170. opentrons/protocol_engine/execution/equipment.py +73 -5
  171. opentrons/protocol_engine/execution/gantry_mover.py +364 -8
  172. opentrons/protocol_engine/execution/movement.py +27 -0
  173. opentrons/protocol_engine/execution/pipetting.py +5 -1
  174. opentrons/protocol_engine/execution/tip_handler.py +4 -6
  175. opentrons/protocol_engine/notes/notes.py +1 -1
  176. opentrons/protocol_engine/protocol_engine.py +7 -6
  177. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  178. opentrons/protocol_engine/resources/labware_validation.py +5 -0
  179. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  180. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  181. opentrons/protocol_engine/slot_standardization.py +9 -9
  182. opentrons/protocol_engine/state/_move_types.py +9 -5
  183. opentrons/protocol_engine/state/_well_math.py +193 -0
  184. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  185. opentrons/protocol_engine/state/command_history.py +12 -0
  186. opentrons/protocol_engine/state/commands.py +17 -13
  187. opentrons/protocol_engine/state/files.py +10 -12
  188. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  189. opentrons/protocol_engine/state/frustum_helpers.py +57 -32
  190. opentrons/protocol_engine/state/geometry.py +47 -1
  191. opentrons/protocol_engine/state/labware.py +79 -25
  192. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  193. opentrons/protocol_engine/state/liquids.py +16 -4
  194. opentrons/protocol_engine/state/modules.py +52 -70
  195. opentrons/protocol_engine/state/motion.py +6 -1
  196. opentrons/protocol_engine/state/pipettes.py +144 -58
  197. opentrons/protocol_engine/state/state.py +21 -2
  198. opentrons/protocol_engine/state/state_summary.py +4 -2
  199. opentrons/protocol_engine/state/tips.py +11 -44
  200. opentrons/protocol_engine/state/update_types.py +343 -48
  201. opentrons/protocol_engine/state/wells.py +19 -11
  202. opentrons/protocol_engine/types.py +176 -28
  203. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  204. opentrons/protocol_reader/file_format_validator.py +5 -5
  205. opentrons/protocol_runner/json_file_reader.py +9 -3
  206. opentrons/protocol_runner/json_translator.py +51 -25
  207. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  208. opentrons/protocol_runner/protocol_runner.py +35 -4
  209. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  210. opentrons/protocol_runner/run_orchestrator.py +13 -3
  211. opentrons/protocols/advanced_control/common.py +38 -0
  212. opentrons/protocols/advanced_control/mix.py +1 -1
  213. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  214. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  215. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  216. opentrons/protocols/api_support/definitions.py +1 -1
  217. opentrons/protocols/api_support/instrument.py +1 -1
  218. opentrons/protocols/api_support/util.py +10 -0
  219. opentrons/protocols/labware.py +39 -6
  220. opentrons/protocols/models/json_protocol.py +5 -9
  221. opentrons/simulate.py +3 -1
  222. opentrons/types.py +162 -2
  223. opentrons/util/logging_config.py +1 -1
  224. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/METADATA +16 -15
  225. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/RECORD +229 -202
  226. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/WHEEL +1 -1
  227. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/LICENSE +0 -0
  228. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/entry_points.txt +0 -0
  229. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/top_level.txt +0 -0
@@ -11,6 +11,7 @@ from opentrons.drivers.asyncio.communication import (
11
11
  SerialConnection,
12
12
  AsyncResponseSerialConnection,
13
13
  AsyncSerial,
14
+ UnhandledGcode,
14
15
  )
15
16
  from opentrons.drivers.thermocycler.abstract import AbstractThermocyclerDriver
16
17
  from opentrons.drivers.types import Temperature, PlateTemperature, ThermocyclerLidStatus
@@ -33,6 +34,7 @@ class GCODE(str, Enum):
33
34
  DEACTIVATE_LID = "M108"
34
35
  DEACTIVATE_BLOCK = "M14"
35
36
  DEVICE_INFO = "M115"
37
+ GET_RESET_REASON = "M114"
36
38
  ENTER_PROGRAMMING = "dfu"
37
39
 
38
40
 
@@ -94,7 +96,7 @@ class ThermocyclerDriverFactory:
94
96
  name=port,
95
97
  ack=TC_GEN2_SERIAL_ACK,
96
98
  retry_wait_time_seconds=0.1,
97
- error_keyword="error",
99
+ error_keyword="err",
98
100
  alarm_keyword="alarm",
99
101
  )
100
102
 
@@ -292,12 +294,13 @@ class ThermocyclerDriver(AbstractThermocyclerDriver):
292
294
 
293
295
  async def get_device_info(self) -> Dict[str, str]:
294
296
  """Send get device info command"""
295
- c = CommandBuilder(terminator=TC_COMMAND_TERMINATOR).add_gcode(
297
+ device_info = CommandBuilder(terminator=TC_COMMAND_TERMINATOR).add_gcode(
296
298
  gcode=GCODE.DEVICE_INFO
297
299
  )
298
300
  response = await self._connection.send_command(
299
- command=c, retries=DEFAULT_COMMAND_RETRIES
301
+ command=device_info, retries=DEFAULT_COMMAND_RETRIES
300
302
  )
303
+
301
304
  return utils.parse_device_information(device_info_string=response)
302
305
 
303
306
  async def enter_programming_mode(self) -> None:
@@ -353,6 +356,17 @@ class ThermocyclerDriverV2(ThermocyclerDriver):
353
356
  response = await self._connection.send_command(
354
357
  command=c, retries=DEFAULT_COMMAND_RETRIES
355
358
  )
359
+
360
+ reset_reason = CommandBuilder(terminator=TC_COMMAND_TERMINATOR).add_gcode(
361
+ gcode=GCODE.GET_RESET_REASON
362
+ )
363
+ try:
364
+ await self._connection.send_command(
365
+ command=reset_reason, retries=DEFAULT_COMMAND_RETRIES
366
+ )
367
+ except UnhandledGcode:
368
+ pass
369
+
356
370
  return utils.parse_hs_device_information(device_info_string=response)
357
371
 
358
372
  async def enter_programming_mode(self) -> None:
opentrons/execute.py CHANGED
@@ -560,7 +560,9 @@ def _create_live_context_pe(
560
560
  # Non-async would use call_soon_threadsafe(), which makes the waiting harder.
561
561
  async def add_all_extra_labware() -> None:
562
562
  for labware_definition_dict in extra_labware.values():
563
- labware_definition = LabwareDefinition.parse_obj(labware_definition_dict)
563
+ labware_definition = LabwareDefinition.model_validate(
564
+ labware_definition_dict
565
+ )
564
566
  pe.add_labware_definition(labware_definition)
565
567
 
566
568
  # Add extra_labware to ProtocolEngine, being careful not to modify ProtocolEngine from this
@@ -38,8 +38,7 @@ OT3HardwareControlAPI = FlexHardwareControlInterface[
38
38
  ]
39
39
  HardwareControlAPI = Union[OT2HardwareControlAPI, OT3HardwareControlAPI]
40
40
 
41
- # this type ignore is because of https://github.com/python/mypy/issues/13437
42
- ThreadManagedHardware = ThreadManager[HardwareControlAPI] # type: ignore[misc]
41
+ ThreadManagedHardware = ThreadManager[HardwareControlAPI]
43
42
  SyncHardwareAPI = SynchronousAdapter[HardwareControlAPI]
44
43
 
45
44
  __all__ = [
@@ -169,6 +169,16 @@ class API(
169
169
  def _reset_last_mount(self) -> None:
170
170
  self._last_moved_mount = None
171
171
 
172
+ def get_deck_from_machine(
173
+ self, machine_pos: Dict[Axis, float]
174
+ ) -> Dict[Axis, float]:
175
+ return deck_from_machine(
176
+ machine_pos=machine_pos,
177
+ attitude=self._robot_calibration.deck_calibration.attitude,
178
+ offset=top_types.Point(0, 0, 0),
179
+ robot_type=cast(RobotType, "OT-2 Standard"),
180
+ )
181
+
172
182
  @classmethod
173
183
  async def build_hardware_controller( # noqa: C901
174
184
  cls,
@@ -657,11 +667,8 @@ class API(
657
667
  async with self._motion_lock:
658
668
  if smoothie_gantry:
659
669
  smoothie_pos.update(await self._backend.home(smoothie_gantry))
660
- self._current_position = deck_from_machine(
661
- machine_pos=self._axis_map_from_string_map(smoothie_pos),
662
- attitude=self._robot_calibration.deck_calibration.attitude,
663
- offset=top_types.Point(0, 0, 0),
664
- robot_type=cast(RobotType, "OT-2 Standard"),
670
+ self._current_position = self.get_deck_from_machine(
671
+ self._axis_map_from_string_map(smoothie_pos)
665
672
  )
666
673
  for plunger in plungers:
667
674
  await self._do_plunger_home(axis=plunger, acquire_lock=False)
@@ -703,11 +710,8 @@ class API(
703
710
  async with self._motion_lock:
704
711
  if refresh:
705
712
  smoothie_pos = await self._backend.update_position()
706
- self._current_position = deck_from_machine(
707
- machine_pos=self._axis_map_from_string_map(smoothie_pos),
708
- attitude=self._robot_calibration.deck_calibration.attitude,
709
- offset=top_types.Point(0, 0, 0),
710
- robot_type=cast(RobotType, "OT-2 Standard"),
713
+ self._current_position = self.get_deck_from_machine(
714
+ self._axis_map_from_string_map(smoothie_pos)
711
715
  )
712
716
  if mount == top_types.Mount.RIGHT:
713
717
  offset = top_types.Point(0, 0, 0)
@@ -917,6 +921,16 @@ class API(
917
921
  async def disengage_axes(self, which: List[Axis]) -> None:
918
922
  await self._backend.disengage_axes([ot2_axis_to_string(ax) for ax in which])
919
923
 
924
+ def axis_is_present(self, axis: Axis) -> bool:
925
+ is_ot2 = axis in Axis.ot2_axes()
926
+ if not is_ot2:
927
+ return False
928
+ if axis in Axis.pipette_axes():
929
+ mount = Axis.to_ot2_mount(axis)
930
+ if self.attached_pipettes.get(mount) is None:
931
+ return False
932
+ return True
933
+
920
934
  @ExecutionManagerProvider.wait_for_running
921
935
  async def _fast_home(self, axes: Sequence[str], margin: float) -> Dict[str, float]:
922
936
  converted_axes = "".join(axes)
@@ -938,11 +952,8 @@ class API(
938
952
 
939
953
  async with self._motion_lock:
940
954
  smoothie_pos = await self._fast_home(smoothie_ax, margin)
941
- self._current_position = deck_from_machine(
942
- machine_pos=self._axis_map_from_string_map(smoothie_pos),
943
- attitude=self._robot_calibration.deck_calibration.attitude,
944
- offset=top_types.Point(0, 0, 0),
945
- robot_type=cast(RobotType, "OT-2 Standard"),
955
+ self._current_position = self.get_deck_from_machine(
956
+ self._axis_map_from_string_map(smoothie_pos)
946
957
  )
947
958
 
948
959
  # Gantry/frame (i.e. not pipette) config API
@@ -1256,11 +1267,8 @@ class API(
1256
1267
  axes=[ot2_axis_to_string(ax) for ax in move.home_axes],
1257
1268
  margin=move.home_after_safety_margin,
1258
1269
  )
1259
- self._current_position = deck_from_machine(
1260
- machine_pos=self._axis_map_from_string_map(smoothie_pos),
1261
- attitude=self._robot_calibration.deck_calibration.attitude,
1262
- offset=top_types.Point(0, 0, 0),
1263
- robot_type=cast(RobotType, "OT-2 Standard"),
1270
+ self._current_position = self.get_deck_from_machine(
1271
+ self._axis_map_from_string_map(smoothie_pos)
1264
1272
  )
1265
1273
 
1266
1274
  for shake in spec.shake_moves:
@@ -36,10 +36,9 @@ from opentrons.hardware_control.types import (
36
36
  HepaFanState,
37
37
  HepaUVState,
38
38
  StatusBarState,
39
+ PipetteSensorResponseQueue,
39
40
  )
40
41
  from opentrons.hardware_control.module_control import AttachedModulesControl
41
- from opentrons_hardware.firmware_bindings.constants import SensorId
42
- from opentrons_hardware.sensors.types import SensorDataType
43
42
  from ..dev_types import OT3AttachedInstruments
44
43
  from .types import HWStopCondition
45
44
 
@@ -60,6 +59,14 @@ class FlexBackend(Protocol):
60
59
  def grab_pressure(self, channels: int, mount: OT3Mount) -> AsyncIterator[None]:
61
60
  ...
62
61
 
62
+ def set_pressure_sensor_available(
63
+ self, pipette_axis: Axis, available: bool
64
+ ) -> None:
65
+ ...
66
+
67
+ def get_pressure_sensor_available(self, pipette_axis: Axis) -> bool:
68
+ ...
69
+
63
70
  def update_constraints_for_gantry_load(self, gantry_load: GantryLoad) -> None:
64
71
  ...
65
72
 
@@ -70,7 +77,11 @@ class FlexBackend(Protocol):
70
77
  ...
71
78
 
72
79
  def update_constraints_for_plunger_acceleration(
73
- self, mount: OT3Mount, acceleration: float, gantry_load: GantryLoad
80
+ self,
81
+ mount: OT3Mount,
82
+ acceleration: float,
83
+ gantry_load: GantryLoad,
84
+ high_speed_pipette: bool = False,
74
85
  ) -> None:
75
86
  ...
76
87
 
@@ -154,11 +165,10 @@ class FlexBackend(Protocol):
154
165
  threshold_pascals: float,
155
166
  plunger_impulse_time: float,
156
167
  num_baseline_reads: int,
168
+ z_offset_for_plunger_prep: float,
157
169
  probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
158
170
  force_both_sensors: bool = False,
159
- response_queue: Optional[
160
- asyncio.Queue[Dict[SensorId, List[SensorDataType]]]
161
- ] = None,
171
+ response_queue: Optional[PipetteSensorResponseQueue] = None,
162
172
  ) -> float:
163
173
  ...
164
174
 
@@ -221,7 +231,7 @@ class FlexBackend(Protocol):
221
231
  ...
222
232
 
223
233
  async def tip_action(
224
- self, origin: Dict[Axis, float], targets: List[Tuple[Dict[Axis, float], float]]
234
+ self, origin: float, targets: List[Tuple[float, float]]
225
235
  ) -> None:
226
236
  ...
227
237
 
@@ -104,7 +104,6 @@ from opentrons_hardware.firmware_bindings.constants import (
104
104
  ErrorCode,
105
105
  SensorId,
106
106
  )
107
- from opentrons_hardware.sensors.types import SensorDataType
108
107
  from opentrons_hardware.firmware_bindings.messages.message_definitions import (
109
108
  StopRequest,
110
109
  )
@@ -142,6 +141,10 @@ from opentrons.hardware_control.types import (
142
141
  EstopState,
143
142
  HardwareEventHandler,
144
143
  HardwareEventUnsubscriber,
144
+ PipetteSensorId,
145
+ PipetteSensorType,
146
+ PipetteSensorData,
147
+ PipetteSensorResponseQueue,
145
148
  )
146
149
  from opentrons.hardware_control.errors import (
147
150
  InvalidPipetteName,
@@ -197,6 +200,7 @@ from opentrons_shared_data.errors.exceptions import (
197
200
  PipetteLiquidNotFoundError,
198
201
  CommunicationError,
199
202
  PythonException,
203
+ UnsupportedHardwareCommand,
200
204
  )
201
205
 
202
206
  from .subsystem_manager import SubsystemManager
@@ -211,6 +215,7 @@ from ..types import HepaFanState, HepaUVState, StatusBarState
211
215
  from .types import HWStopCondition
212
216
  from .flex_protocol import FlexBackend
213
217
  from .status_bar_state import StatusBarStateController
218
+ from opentrons_hardware.sensors.types import SensorDataType
214
219
 
215
220
  log = logging.getLogger(__name__)
216
221
 
@@ -362,6 +367,7 @@ class OT3Controller(FlexBackend):
362
367
  self._configuration.motion_settings, GantryLoad.LOW_THROUGHPUT
363
368
  )
364
369
  )
370
+ self._pressure_sensor_available: Dict[NodeId, bool] = {}
365
371
 
366
372
  @asynccontextmanager
367
373
  async def restore_system_constraints(self) -> AsyncIterator[None]:
@@ -380,6 +386,16 @@ class OT3Controller(FlexBackend):
380
386
  async with grab_pressure(channels, tool, self._messenger):
381
387
  yield
382
388
 
389
+ def set_pressure_sensor_available(
390
+ self, pipette_axis: Axis, available: bool
391
+ ) -> None:
392
+ pip_node = axis_to_node(pipette_axis)
393
+ self._pressure_sensor_available[pip_node] = available
394
+
395
+ def get_pressure_sensor_available(self, pipette_axis: Axis) -> bool:
396
+ pip_node = axis_to_node(pipette_axis)
397
+ return self._pressure_sensor_available[pip_node]
398
+
383
399
  def update_constraints_for_calibration_with_gantry_load(
384
400
  self,
385
401
  gantry_load: GantryLoad,
@@ -399,10 +415,18 @@ class OT3Controller(FlexBackend):
399
415
  )
400
416
 
401
417
  def update_constraints_for_plunger_acceleration(
402
- self, mount: OT3Mount, acceleration: float, gantry_load: GantryLoad
418
+ self,
419
+ mount: OT3Mount,
420
+ acceleration: float,
421
+ gantry_load: GantryLoad,
422
+ high_speed_pipette: bool = False,
403
423
  ) -> None:
404
424
  new_constraints = get_system_constraints_for_plunger_acceleration(
405
- self._configuration.motion_settings, gantry_load, mount, acceleration
425
+ self._configuration.motion_settings,
426
+ gantry_load,
427
+ mount,
428
+ acceleration,
429
+ high_speed_pipette,
406
430
  )
407
431
  self._move_manager.update_constraints(new_constraints)
408
432
 
@@ -597,30 +621,104 @@ class OT3Controller(FlexBackend):
597
621
  return axis_convert(self._encoder_position, 0.0)
598
622
 
599
623
  def _handle_motor_status_response(
600
- self,
601
- response: NodeMap[MotorPositionStatus],
624
+ self, response: NodeMap[MotorPositionStatus], handle_gear_move: bool = False
602
625
  ) -> None:
603
626
  for axis, pos in response.items():
604
- self._position.update({axis: pos.motor_position})
605
- self._encoder_position.update({axis: pos.encoder_position})
606
- # TODO (FPS 6-01-2023): Remove this once the Feature Flag to ignore stall detection is removed.
607
- # This check will latch the motor status for an axis at "true" if it was ever set to true.
608
- # To account for the case where a motor axis has its power reset, we also depend on the
609
- # "encoder_ok" flag staying set (it will only be False if the motor axis has not been
610
- # homed since a power cycle)
611
- motor_ok_latch = (
612
- (not self._feature_flags.stall_detection_enabled)
613
- and ((axis in self._motor_status) and self._motor_status[axis].motor_ok)
614
- and self._motor_status[axis].encoder_ok
615
- )
616
- self._motor_status.update(
617
- {
618
- axis: MotorStatus(
619
- motor_ok=(pos.motor_ok or motor_ok_latch),
620
- encoder_ok=pos.encoder_ok,
627
+ if handle_gear_move and axis == NodeId.pipette_left:
628
+ self._gear_motor_position = {axis: pos.motor_position}
629
+ else:
630
+ self._position.update({axis: pos.motor_position})
631
+ self._encoder_position.update({axis: pos.encoder_position})
632
+ # TODO (FPS 6-01-2023): Remove this once the Feature Flag to ignore stall detection is removed.
633
+ # This check will latch the motor status for an axis at "true" if it was ever set to true.
634
+ # To account for the case where a motor axis has its power reset, we also depend on the
635
+ # "encoder_ok" flag staying set (it will only be False if the motor axis has not been
636
+ # homed since a power cycle)
637
+ motor_ok_latch = (
638
+ (not self._feature_flags.stall_detection_enabled)
639
+ and (
640
+ (axis in self._motor_status)
641
+ and self._motor_status[axis].motor_ok
621
642
  )
622
- }
643
+ and self._motor_status[axis].encoder_ok
644
+ )
645
+ self._motor_status.update(
646
+ {
647
+ axis: MotorStatus(
648
+ motor_ok=(pos.motor_ok or motor_ok_latch),
649
+ encoder_ok=pos.encoder_ok,
650
+ )
651
+ }
652
+ )
653
+
654
+ def _build_move_node_axis_runner(
655
+ self,
656
+ origin: Dict[Axis, float],
657
+ target: Dict[Axis, float],
658
+ speed: float,
659
+ stop_condition: HWStopCondition,
660
+ nodes_in_moves_only: bool,
661
+ ) -> Tuple[Optional[MoveGroupRunner], bool]:
662
+ if not target:
663
+ return None, False
664
+ move_target = MoveTarget.build(position=target, max_speed=speed)
665
+ try:
666
+ _, movelist = self._move_manager.plan_motion(
667
+ origin=origin, target_list=[move_target]
623
668
  )
669
+ except ZeroLengthMoveError as zme:
670
+ log.debug(f"Not moving because move was zero length {str(zme)}")
671
+ return None, False
672
+ moves = movelist[0]
673
+ log.debug(
674
+ f"move: machine coordinates {target} from origin: machine coordinates {origin} at speed: {speed} requires {moves}"
675
+ )
676
+
677
+ ordered_nodes = self._motor_nodes()
678
+ if nodes_in_moves_only:
679
+ moving_axes = {
680
+ axis_to_node(ax) for move in moves for ax in move.unit_vector.keys()
681
+ }
682
+ ordered_nodes = ordered_nodes.intersection(moving_axes)
683
+
684
+ move_group, _ = create_move_group(
685
+ origin, moves, ordered_nodes, MoveStopCondition[stop_condition.name]
686
+ )
687
+ return (
688
+ MoveGroupRunner(
689
+ move_groups=[move_group],
690
+ ignore_stalls=True
691
+ if not self._feature_flags.stall_detection_enabled
692
+ else False,
693
+ ),
694
+ False,
695
+ )
696
+
697
+ def _build_move_gear_axis_runner(
698
+ self,
699
+ possible_q_axis_origin: Optional[float],
700
+ possible_q_axis_target: Optional[float],
701
+ speed: float,
702
+ nodes_in_moves_only: bool,
703
+ ) -> Tuple[Optional[MoveGroupRunner], bool]:
704
+ if possible_q_axis_origin is None or possible_q_axis_target is None:
705
+ return None, True
706
+ tip_motor_move_group = self._build_tip_action_group(
707
+ possible_q_axis_origin, [(possible_q_axis_target, speed)]
708
+ )
709
+ if nodes_in_moves_only:
710
+ ordered_nodes = self._motor_nodes()
711
+
712
+ ordered_nodes.intersection({axis_to_node(Axis.Q)})
713
+ return (
714
+ MoveGroupRunner(
715
+ move_groups=[tip_motor_move_group],
716
+ ignore_stalls=True
717
+ if not self._feature_flags.stall_detection_enabled
718
+ else False,
719
+ ),
720
+ True,
721
+ )
624
722
 
625
723
  @requires_update
626
724
  @requires_estop
@@ -648,40 +746,52 @@ class OT3Controller(FlexBackend):
648
746
  Returns:
649
747
  None
650
748
  """
651
- move_target = MoveTarget.build(position=target, max_speed=speed)
652
- try:
653
- _, movelist = self._move_manager.plan_motion(
654
- origin=origin, target_list=[move_target]
655
- )
656
- except ZeroLengthMoveError as zme:
657
- log.debug(f"Not moving because move was zero length {str(zme)}")
658
- return
659
- moves = movelist[0]
660
- log.info(f"move: machine {target} from {origin} requires {moves}")
749
+ possible_q_axis_origin = origin.pop(Axis.Q, None)
750
+ possible_q_axis_target = target.pop(Axis.Q, None)
661
751
 
662
- ordered_nodes = self._motor_nodes()
663
- if nodes_in_moves_only:
664
- moving_axes = {
665
- axis_to_node(ax) for move in moves for ax in move.unit_vector.keys()
666
- }
667
- ordered_nodes = ordered_nodes.intersection(moving_axes)
668
-
669
- group = create_move_group(
670
- origin, moves, ordered_nodes, MoveStopCondition[stop_condition.name]
752
+ maybe_runners = (
753
+ self._build_move_node_axis_runner(
754
+ origin, target, speed, stop_condition, nodes_in_moves_only
755
+ ),
756
+ self._build_move_gear_axis_runner(
757
+ possible_q_axis_origin,
758
+ possible_q_axis_target,
759
+ speed,
760
+ nodes_in_moves_only,
761
+ ),
671
762
  )
672
- move_group, _ = group
673
- runner = MoveGroupRunner(
674
- move_groups=[move_group],
675
- ignore_stalls=True
676
- if not self._feature_flags.stall_detection_enabled
677
- else False,
763
+ log.debug(f"The move groups are {maybe_runners}.")
764
+
765
+ gather_moving_nodes = set()
766
+ all_moving_nodes = set()
767
+ for runner, _ in maybe_runners:
768
+ if runner:
769
+ for n in runner.all_nodes():
770
+ gather_moving_nodes.add(n)
771
+ for n in runner.all_moving_nodes():
772
+ all_moving_nodes.add(n)
773
+
774
+ pipettes_moving = moving_pipettes_in_move_group(
775
+ gather_moving_nodes, all_moving_nodes
678
776
  )
679
777
 
680
- pipettes_moving = moving_pipettes_in_move_group(move_group)
681
-
682
- async with self._monitor_overpressure(pipettes_moving):
778
+ async def _runner_coroutine(
779
+ runner: MoveGroupRunner, is_gear_move: bool
780
+ ) -> Tuple[Dict[NodeId, MotorPositionStatus], bool]:
683
781
  positions = await runner.run(can_messenger=self._messenger)
684
- self._handle_motor_status_response(positions)
782
+ return positions, is_gear_move
783
+
784
+ coros = [
785
+ _runner_coroutine(runner, is_gear_move)
786
+ for runner, is_gear_move in maybe_runners
787
+ if runner
788
+ ]
789
+ checked_moving_pipettes = self._pipettes_to_monitor_pressure(pipettes_moving)
790
+ async with self._monitor_overpressure(checked_moving_pipettes):
791
+ all_positions = await asyncio.gather(*coros)
792
+
793
+ for positions, handle_gear_move in all_positions:
794
+ self._handle_motor_status_response(positions, handle_gear_move)
685
795
 
686
796
  def _get_axis_home_distance(self, axis: Axis) -> float:
687
797
  if self.check_motor_status([axis]):
@@ -786,7 +896,8 @@ class OT3Controller(FlexBackend):
786
896
  moving_pipettes = [
787
897
  axis_to_node(ax) for ax in checked_axes if ax in Axis.pipette_axes()
788
898
  ]
789
- async with self._monitor_overpressure(moving_pipettes):
899
+ checked_moving_pipettes = self._pipettes_to_monitor_pressure(moving_pipettes)
900
+ async with self._monitor_overpressure(checked_moving_pipettes):
790
901
  positions = await asyncio.gather(*coros)
791
902
  # TODO(CM): default gear motor homing routine to have some acceleration
792
903
  if Axis.Q in checked_axes:
@@ -796,10 +907,14 @@ class OT3Controller(FlexBackend):
796
907
  Axis.to_kind(Axis.Q)
797
908
  ],
798
909
  )
910
+
799
911
  for position in positions:
800
912
  self._handle_motor_status_response(position)
801
913
  return axis_convert(self._position, 0.0)
802
914
 
915
+ def _pipettes_to_monitor_pressure(self, pipettes: List[NodeId]) -> List[NodeId]:
916
+ return [pip for pip in pipettes if self._pressure_sensor_available[pip]]
917
+
803
918
  def _filter_move_group(self, move_group: MoveGroup) -> MoveGroup:
804
919
  new_group: MoveGroup = []
805
920
  for step in move_group:
@@ -840,17 +955,23 @@ class OT3Controller(FlexBackend):
840
955
  self._gear_motor_position = {}
841
956
  raise e
842
957
 
843
- async def tip_action(
844
- self, origin: Dict[Axis, float], targets: List[Tuple[Dict[Axis, float], float]]
845
- ) -> None:
958
+ def _build_tip_action_group(
959
+ self, origin: float, targets: List[Tuple[float, float]]
960
+ ) -> MoveGroup:
846
961
  move_targets = [
847
- MoveTarget.build(target_pos, speed) for target_pos, speed in targets
962
+ MoveTarget.build({Axis.Q: target_pos}, speed)
963
+ for target_pos, speed in targets
848
964
  ]
849
965
  _, moves = self._move_manager.plan_motion(
850
- origin=origin, target_list=move_targets
966
+ origin={Axis.Q: origin}, target_list=move_targets
851
967
  )
852
- move_group = create_tip_action_group(moves[0], [NodeId.pipette_left], "clamp")
853
968
 
969
+ return create_tip_action_group(moves[0], [NodeId.pipette_left], "clamp")
970
+
971
+ async def tip_action(
972
+ self, origin: float, targets: List[Tuple[float, float]]
973
+ ) -> None:
974
+ move_group = self._build_tip_action_group(origin, targets)
854
975
  runner = MoveGroupRunner(
855
976
  move_groups=[move_group],
856
977
  ignore_stalls=True
@@ -915,10 +1036,12 @@ class OT3Controller(FlexBackend):
915
1036
  lookup_name = {
916
1037
  FirmwarePipetteName.p1000_single: "P1KS",
917
1038
  FirmwarePipetteName.p1000_multi: "P1KM",
1039
+ FirmwarePipetteName.p1000_multi_em: "P1KP",
918
1040
  FirmwarePipetteName.p50_single: "P50S",
919
1041
  FirmwarePipetteName.p50_multi: "P50M",
920
1042
  FirmwarePipetteName.p1000_96: "P1KH",
921
1043
  FirmwarePipetteName.p50_96: "P50H",
1044
+ FirmwarePipetteName.p200_96: "P2HH",
922
1045
  }
923
1046
  return lookup_name[pipette_name]
924
1047
 
@@ -949,6 +1072,7 @@ class OT3Controller(FlexBackend):
949
1072
  converted_name.pipette_type,
950
1073
  converted_name.pipette_channels,
951
1074
  converted_name.pipette_version,
1075
+ converted_name.oem_type,
952
1076
  ),
953
1077
  "id": OT3Controller._combine_serial_number(attached),
954
1078
  }
@@ -1370,14 +1494,39 @@ class OT3Controller(FlexBackend):
1370
1494
  threshold_pascals: float,
1371
1495
  plunger_impulse_time: float,
1372
1496
  num_baseline_reads: int,
1497
+ z_offset_for_plunger_prep: float,
1373
1498
  probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
1374
1499
  force_both_sensors: bool = False,
1375
- response_queue: Optional[
1376
- asyncio.Queue[Dict[SensorId, List[SensorDataType]]]
1377
- ] = None,
1500
+ response_queue: Optional[PipetteSensorResponseQueue] = None,
1378
1501
  ) -> float:
1379
1502
  head_node = axis_to_node(Axis.by_mount(mount))
1380
1503
  tool = sensor_node_for_pipette(OT3Mount(mount.value))
1504
+ if tool not in self._pipettes_to_monitor_pressure([tool]):
1505
+ raise UnsupportedHardwareCommand(
1506
+ "Liquid Presence Detection not available on this pipette."
1507
+ )
1508
+
1509
+ if response_queue is None:
1510
+ response_capture: Optional[
1511
+ Callable[[Dict[SensorId, List[SensorDataType]]], None]
1512
+ ] = None
1513
+ else:
1514
+
1515
+ def response_capture(data: Dict[SensorId, List[SensorDataType]]) -> None:
1516
+ response_queue.put_nowait(
1517
+ {
1518
+ PipetteSensorId(sensor_id.value): [
1519
+ PipetteSensorData(
1520
+ sensor_type=PipetteSensorType(packet.sensor_type.value),
1521
+ _as_int=packet.to_int,
1522
+ _as_float=packet.to_float(),
1523
+ )
1524
+ for packet in packets
1525
+ ]
1526
+ for sensor_id, packets in data.items()
1527
+ }
1528
+ )
1529
+
1381
1530
  positions = await liquid_probe(
1382
1531
  messenger=self._messenger,
1383
1532
  tool=tool,
@@ -1388,9 +1537,10 @@ class OT3Controller(FlexBackend):
1388
1537
  threshold_pascals=threshold_pascals,
1389
1538
  plunger_impulse_time=plunger_impulse_time,
1390
1539
  num_baseline_reads=num_baseline_reads,
1540
+ z_offset_for_plunger_prep=z_offset_for_plunger_prep,
1391
1541
  sensor_id=sensor_id_for_instrument(probe),
1392
1542
  force_both_sensors=force_both_sensors,
1393
- response_queue=response_queue,
1543
+ emplace_data=response_capture,
1394
1544
  )
1395
1545
  for node, point in positions.items():
1396
1546
  self._position.update({node: point.motor_position})