opentrons 8.3.0a0__py2.py3-none-any.whl → 8.3.0a2__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.
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/drivers/asyncio/communication/__init__.py +2 -0
  9. opentrons/drivers/asyncio/communication/errors.py +16 -3
  10. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  11. opentrons/drivers/command_builder.py +2 -2
  12. opentrons/drivers/flex_stacker/__init__.py +9 -0
  13. opentrons/drivers/flex_stacker/abstract.py +89 -0
  14. opentrons/drivers/flex_stacker/driver.py +260 -0
  15. opentrons/drivers/flex_stacker/simulator.py +109 -0
  16. opentrons/drivers/flex_stacker/types.py +138 -0
  17. opentrons/drivers/heater_shaker/driver.py +18 -3
  18. opentrons/drivers/temp_deck/driver.py +13 -3
  19. opentrons/drivers/thermocycler/driver.py +17 -3
  20. opentrons/execute.py +3 -1
  21. opentrons/hardware_control/__init__.py +1 -2
  22. opentrons/hardware_control/api.py +28 -20
  23. opentrons/hardware_control/backends/flex_protocol.py +4 -6
  24. opentrons/hardware_control/backends/ot3controller.py +177 -59
  25. opentrons/hardware_control/backends/ot3simulator.py +10 -8
  26. opentrons/hardware_control/backends/ot3utils.py +3 -13
  27. opentrons/hardware_control/dev_types.py +2 -0
  28. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  29. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  30. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  31. opentrons/hardware_control/emulation/settings.py +3 -4
  32. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  33. opentrons/hardware_control/instruments/ot2/pipette.py +9 -21
  34. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  35. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  36. opentrons/hardware_control/instruments/ot3/pipette.py +13 -22
  37. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  38. opentrons/hardware_control/modules/mod_abc.py +2 -2
  39. opentrons/hardware_control/motion_utilities.py +68 -0
  40. opentrons/hardware_control/nozzle_manager.py +39 -41
  41. opentrons/hardware_control/ot3_calibration.py +1 -1
  42. opentrons/hardware_control/ot3api.py +34 -22
  43. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  44. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  45. opentrons/hardware_control/protocols/liquid_handler.py +18 -0
  46. opentrons/hardware_control/protocols/motion_controller.py +6 -0
  47. opentrons/hardware_control/robot_calibration.py +1 -1
  48. opentrons/hardware_control/types.py +61 -0
  49. opentrons/protocol_api/__init__.py +20 -1
  50. opentrons/protocol_api/_liquid.py +24 -49
  51. opentrons/protocol_api/_liquid_properties.py +754 -0
  52. opentrons/protocol_api/_types.py +24 -0
  53. opentrons/protocol_api/core/common.py +2 -0
  54. opentrons/protocol_api/core/engine/instrument.py +67 -10
  55. opentrons/protocol_api/core/engine/labware.py +29 -7
  56. opentrons/protocol_api/core/engine/protocol.py +130 -5
  57. opentrons/protocol_api/core/engine/robot.py +139 -0
  58. opentrons/protocol_api/core/engine/well.py +4 -1
  59. opentrons/protocol_api/core/instrument.py +42 -4
  60. opentrons/protocol_api/core/labware.py +13 -4
  61. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +34 -3
  62. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  63. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  64. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  65. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +34 -3
  66. opentrons/protocol_api/core/protocol.py +34 -1
  67. opentrons/protocol_api/core/robot.py +51 -0
  68. opentrons/protocol_api/instrument_context.py +145 -43
  69. opentrons/protocol_api/labware.py +231 -7
  70. opentrons/protocol_api/module_contexts.py +21 -17
  71. opentrons/protocol_api/protocol_context.py +125 -4
  72. opentrons/protocol_api/robot_context.py +204 -32
  73. opentrons/protocol_api/validation.py +261 -3
  74. opentrons/protocol_engine/__init__.py +4 -0
  75. opentrons/protocol_engine/actions/actions.py +2 -3
  76. opentrons/protocol_engine/clients/sync_client.py +18 -0
  77. opentrons/protocol_engine/commands/__init__.py +81 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
  79. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
  80. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
  81. opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
  82. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  83. opentrons/protocol_engine/commands/aspirate.py +103 -53
  84. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  85. opentrons/protocol_engine/commands/blow_out.py +44 -39
  86. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  87. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  88. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  89. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  90. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  91. opentrons/protocol_engine/commands/command.py +73 -66
  92. opentrons/protocol_engine/commands/command_unions.py +101 -1
  93. opentrons/protocol_engine/commands/comment.py +1 -1
  94. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  95. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  96. opentrons/protocol_engine/commands/custom.py +6 -12
  97. opentrons/protocol_engine/commands/dispense.py +82 -48
  98. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  99. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  100. opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
  101. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  102. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  103. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  104. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  105. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  106. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  107. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  108. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  109. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  111. opentrons/protocol_engine/commands/home.py +13 -4
  112. opentrons/protocol_engine/commands/liquid_probe.py +60 -25
  113. opentrons/protocol_engine/commands/load_labware.py +29 -7
  114. opentrons/protocol_engine/commands/load_lid.py +146 -0
  115. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  116. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  117. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  118. opentrons/protocol_engine/commands/load_module.py +31 -10
  119. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  120. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  121. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  122. opentrons/protocol_engine/commands/move_labware.py +19 -6
  123. opentrons/protocol_engine/commands/move_relative.py +35 -25
  124. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  125. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  126. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  127. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  128. opentrons/protocol_engine/commands/movement_common.py +338 -0
  129. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  130. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  131. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  132. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  133. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  134. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  135. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  136. opentrons/protocol_engine/commands/robot/common.py +18 -0
  137. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  138. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  139. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  140. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  141. opentrons/protocol_engine/commands/save_position.py +14 -5
  142. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  143. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  144. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  145. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  146. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  147. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  148. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  149. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  150. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  151. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
  152. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  153. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  154. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  157. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  158. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
  159. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
  160. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
  161. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
  162. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  163. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  164. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  165. opentrons/protocol_engine/errors/__init__.py +8 -0
  166. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  167. opentrons/protocol_engine/errors/exceptions.py +50 -0
  168. opentrons/protocol_engine/execution/command_executor.py +1 -1
  169. opentrons/protocol_engine/execution/equipment.py +73 -5
  170. opentrons/protocol_engine/execution/gantry_mover.py +364 -8
  171. opentrons/protocol_engine/execution/movement.py +27 -0
  172. opentrons/protocol_engine/execution/pipetting.py +5 -1
  173. opentrons/protocol_engine/execution/tip_handler.py +4 -6
  174. opentrons/protocol_engine/notes/notes.py +1 -1
  175. opentrons/protocol_engine/protocol_engine.py +7 -6
  176. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  177. opentrons/protocol_engine/resources/labware_validation.py +5 -0
  178. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  179. opentrons/protocol_engine/resources/pipette_data_provider.py +12 -0
  180. opentrons/protocol_engine/slot_standardization.py +9 -9
  181. opentrons/protocol_engine/state/_move_types.py +9 -5
  182. opentrons/protocol_engine/state/_well_math.py +193 -0
  183. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  184. opentrons/protocol_engine/state/command_history.py +12 -0
  185. opentrons/protocol_engine/state/commands.py +17 -13
  186. opentrons/protocol_engine/state/files.py +10 -12
  187. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  188. opentrons/protocol_engine/state/frustum_helpers.py +57 -32
  189. opentrons/protocol_engine/state/geometry.py +47 -1
  190. opentrons/protocol_engine/state/labware.py +79 -25
  191. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  192. opentrons/protocol_engine/state/liquids.py +16 -4
  193. opentrons/protocol_engine/state/modules.py +52 -70
  194. opentrons/protocol_engine/state/motion.py +6 -1
  195. opentrons/protocol_engine/state/pipettes.py +135 -58
  196. opentrons/protocol_engine/state/state.py +21 -2
  197. opentrons/protocol_engine/state/state_summary.py +4 -2
  198. opentrons/protocol_engine/state/tips.py +11 -44
  199. opentrons/protocol_engine/state/update_types.py +343 -48
  200. opentrons/protocol_engine/state/wells.py +19 -11
  201. opentrons/protocol_engine/types.py +176 -28
  202. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  203. opentrons/protocol_reader/file_format_validator.py +5 -5
  204. opentrons/protocol_runner/json_file_reader.py +9 -3
  205. opentrons/protocol_runner/json_translator.py +51 -25
  206. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  207. opentrons/protocol_runner/protocol_runner.py +35 -4
  208. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  209. opentrons/protocol_runner/run_orchestrator.py +13 -3
  210. opentrons/protocols/advanced_control/common.py +38 -0
  211. opentrons/protocols/advanced_control/mix.py +1 -1
  212. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  213. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  214. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  215. opentrons/protocols/api_support/definitions.py +1 -1
  216. opentrons/protocols/api_support/instrument.py +1 -1
  217. opentrons/protocols/api_support/util.py +10 -0
  218. opentrons/protocols/labware.py +70 -8
  219. opentrons/protocols/models/json_protocol.py +5 -9
  220. opentrons/simulate.py +3 -1
  221. opentrons/types.py +162 -2
  222. opentrons/util/entrypoint_util.py +2 -5
  223. opentrons/util/logging_config.py +1 -1
  224. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/METADATA +16 -15
  225. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/RECORD +229 -202
  226. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/WHEEL +1 -1
  227. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/LICENSE +0 -0
  228. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/entry_points.txt +0 -0
  229. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.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
 
@@ -166,11 +165,10 @@ class FlexBackend(Protocol):
166
165
  threshold_pascals: float,
167
166
  plunger_impulse_time: float,
168
167
  num_baseline_reads: int,
168
+ z_offset_for_plunger_prep: float,
169
169
  probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
170
170
  force_both_sensors: bool = False,
171
- response_queue: Optional[
172
- asyncio.Queue[Dict[SensorId, List[SensorDataType]]]
173
- ] = None,
171
+ response_queue: Optional[PipetteSensorResponseQueue] = None,
174
172
  ) -> float:
175
173
  ...
176
174
 
@@ -233,7 +231,7 @@ class FlexBackend(Protocol):
233
231
  ...
234
232
 
235
233
  async def tip_action(
236
- self, origin: Dict[Axis, float], targets: List[Tuple[Dict[Axis, float], float]]
234
+ self, origin: float, targets: List[Tuple[float, float]]
237
235
  ) -> None:
238
236
  ...
239
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,
@@ -212,6 +215,7 @@ from ..types import HepaFanState, HepaUVState, StatusBarState
212
215
  from .types import HWStopCondition
213
216
  from .flex_protocol import FlexBackend
214
217
  from .status_bar_state import StatusBarStateController
218
+ from opentrons_hardware.sensors.types import SensorDataType
215
219
 
216
220
  log = logging.getLogger(__name__)
217
221
 
@@ -617,30 +621,104 @@ class OT3Controller(FlexBackend):
617
621
  return axis_convert(self._encoder_position, 0.0)
618
622
 
619
623
  def _handle_motor_status_response(
620
- self,
621
- response: NodeMap[MotorPositionStatus],
624
+ self, response: NodeMap[MotorPositionStatus], handle_gear_move: bool = False
622
625
  ) -> None:
623
626
  for axis, pos in response.items():
624
- self._position.update({axis: pos.motor_position})
625
- self._encoder_position.update({axis: pos.encoder_position})
626
- # TODO (FPS 6-01-2023): Remove this once the Feature Flag to ignore stall detection is removed.
627
- # This check will latch the motor status for an axis at "true" if it was ever set to true.
628
- # To account for the case where a motor axis has its power reset, we also depend on the
629
- # "encoder_ok" flag staying set (it will only be False if the motor axis has not been
630
- # homed since a power cycle)
631
- motor_ok_latch = (
632
- (not self._feature_flags.stall_detection_enabled)
633
- and ((axis in self._motor_status) and self._motor_status[axis].motor_ok)
634
- and self._motor_status[axis].encoder_ok
635
- )
636
- self._motor_status.update(
637
- {
638
- axis: MotorStatus(
639
- motor_ok=(pos.motor_ok or motor_ok_latch),
640
- 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
641
642
  )
642
- }
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]
643
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
+ )
644
722
 
645
723
  @requires_update
646
724
  @requires_estop
@@ -668,41 +746,52 @@ class OT3Controller(FlexBackend):
668
746
  Returns:
669
747
  None
670
748
  """
671
- move_target = MoveTarget.build(position=target, max_speed=speed)
672
- try:
673
- _, movelist = self._move_manager.plan_motion(
674
- origin=origin, target_list=[move_target]
675
- )
676
- except ZeroLengthMoveError as zme:
677
- log.debug(f"Not moving because move was zero length {str(zme)}")
678
- return
679
- moves = movelist[0]
680
- 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)
681
751
 
682
- ordered_nodes = self._motor_nodes()
683
- if nodes_in_moves_only:
684
- moving_axes = {
685
- axis_to_node(ax) for move in moves for ax in move.unit_vector.keys()
686
- }
687
- ordered_nodes = ordered_nodes.intersection(moving_axes)
688
-
689
- group = create_move_group(
690
- 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
+ ),
691
762
  )
692
- move_group, _ = group
693
- runner = MoveGroupRunner(
694
- move_groups=[move_group],
695
- ignore_stalls=True
696
- if not self._feature_flags.stall_detection_enabled
697
- 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
698
776
  )
699
777
 
700
- pipettes_moving = moving_pipettes_in_move_group(move_group)
778
+ async def _runner_coroutine(
779
+ runner: MoveGroupRunner, is_gear_move: bool
780
+ ) -> Tuple[Dict[NodeId, MotorPositionStatus], bool]:
781
+ positions = await runner.run(can_messenger=self._messenger)
782
+ return positions, is_gear_move
701
783
 
784
+ coros = [
785
+ _runner_coroutine(runner, is_gear_move)
786
+ for runner, is_gear_move in maybe_runners
787
+ if runner
788
+ ]
702
789
  checked_moving_pipettes = self._pipettes_to_monitor_pressure(pipettes_moving)
703
790
  async with self._monitor_overpressure(checked_moving_pipettes):
704
- positions = await runner.run(can_messenger=self._messenger)
705
- self._handle_motor_status_response(positions)
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)
706
795
 
707
796
  def _get_axis_home_distance(self, axis: Axis) -> float:
708
797
  if self.check_motor_status([axis]):
@@ -818,6 +907,7 @@ class OT3Controller(FlexBackend):
818
907
  Axis.to_kind(Axis.Q)
819
908
  ],
820
909
  )
910
+
821
911
  for position in positions:
822
912
  self._handle_motor_status_response(position)
823
913
  return axis_convert(self._position, 0.0)
@@ -865,17 +955,23 @@ class OT3Controller(FlexBackend):
865
955
  self._gear_motor_position = {}
866
956
  raise e
867
957
 
868
- async def tip_action(
869
- self, origin: Dict[Axis, float], targets: List[Tuple[Dict[Axis, float], float]]
870
- ) -> None:
958
+ def _build_tip_action_group(
959
+ self, origin: float, targets: List[Tuple[float, float]]
960
+ ) -> MoveGroup:
871
961
  move_targets = [
872
- 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
873
964
  ]
874
965
  _, moves = self._move_manager.plan_motion(
875
- origin=origin, target_list=move_targets
966
+ origin={Axis.Q: origin}, target_list=move_targets
876
967
  )
877
- move_group = create_tip_action_group(moves[0], [NodeId.pipette_left], "clamp")
878
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)
879
975
  runner = MoveGroupRunner(
880
976
  move_groups=[move_group],
881
977
  ignore_stalls=True
@@ -945,6 +1041,7 @@ class OT3Controller(FlexBackend):
945
1041
  FirmwarePipetteName.p50_multi: "P50M",
946
1042
  FirmwarePipetteName.p1000_96: "P1KH",
947
1043
  FirmwarePipetteName.p50_96: "P50H",
1044
+ FirmwarePipetteName.p200_96: "P2HH",
948
1045
  }
949
1046
  return lookup_name[pipette_name]
950
1047
 
@@ -1397,11 +1494,10 @@ class OT3Controller(FlexBackend):
1397
1494
  threshold_pascals: float,
1398
1495
  plunger_impulse_time: float,
1399
1496
  num_baseline_reads: int,
1497
+ z_offset_for_plunger_prep: float,
1400
1498
  probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
1401
1499
  force_both_sensors: bool = False,
1402
- response_queue: Optional[
1403
- asyncio.Queue[Dict[SensorId, List[SensorDataType]]]
1404
- ] = None,
1500
+ response_queue: Optional[PipetteSensorResponseQueue] = None,
1405
1501
  ) -> float:
1406
1502
  head_node = axis_to_node(Axis.by_mount(mount))
1407
1503
  tool = sensor_node_for_pipette(OT3Mount(mount.value))
@@ -1410,6 +1506,27 @@ class OT3Controller(FlexBackend):
1410
1506
  "Liquid Presence Detection not available on this pipette."
1411
1507
  )
1412
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
+
1413
1530
  positions = await liquid_probe(
1414
1531
  messenger=self._messenger,
1415
1532
  tool=tool,
@@ -1420,9 +1537,10 @@ class OT3Controller(FlexBackend):
1420
1537
  threshold_pascals=threshold_pascals,
1421
1538
  plunger_impulse_time=plunger_impulse_time,
1422
1539
  num_baseline_reads=num_baseline_reads,
1540
+ z_offset_for_plunger_prep=z_offset_for_plunger_prep,
1423
1541
  sensor_id=sensor_id_for_instrument(probe),
1424
1542
  force_both_sensors=force_both_sensors,
1425
- response_queue=response_queue,
1543
+ emplace_data=response_capture,
1426
1544
  )
1427
1545
  for node, point in positions.items():
1428
1546
  self._position.update({node: point.motor_position})
@@ -45,6 +45,7 @@ from opentrons.hardware_control.types import (
45
45
  EstopPhysicalStatus,
46
46
  HardwareEventHandler,
47
47
  HardwareEventUnsubscriber,
48
+ PipetteSensorResponseQueue,
48
49
  )
49
50
 
50
51
  from opentrons_shared_data.pipette.types import PipetteName, PipetteModel
@@ -62,9 +63,9 @@ from opentrons.hardware_control.dev_types import (
62
63
  )
63
64
  from opentrons.util.async_helpers import ensure_yield
64
65
  from .types import HWStopCondition
65
- from .flex_protocol import FlexBackend
66
- from opentrons_hardware.firmware_bindings.constants import SensorId
67
- from opentrons_hardware.sensors.types import SensorDataType
66
+ from .flex_protocol import (
67
+ FlexBackend,
68
+ )
68
69
 
69
70
  log = logging.getLogger(__name__)
70
71
 
@@ -352,11 +353,10 @@ class OT3Simulator(FlexBackend):
352
353
  threshold_pascals: float,
353
354
  plunger_impulse_time: float,
354
355
  num_baseline_reads: int,
356
+ z_offset_for_plunger_prep: float,
355
357
  probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
356
358
  force_both_sensors: bool = False,
357
- response_queue: Optional[
358
- asyncio.Queue[Dict[SensorId, List[SensorDataType]]]
359
- ] = None,
359
+ response_queue: Optional[PipetteSensorResponseQueue] = None,
360
360
  ) -> float:
361
361
  z_axis = Axis.by_mount(mount)
362
362
  pos = self._position
@@ -443,10 +443,12 @@ class OT3Simulator(FlexBackend):
443
443
  return self._sim_jaw_state
444
444
 
445
445
  async def tip_action(
446
- self, origin: Dict[Axis, float], targets: List[Tuple[Dict[Axis, float], float]]
446
+ self, origin: float, targets: List[Tuple[float, float]]
447
447
  ) -> None:
448
448
  self._gear_motor_position.update(
449
- coalesce_move_segments(origin, [target[0] for target in targets])
449
+ coalesce_move_segments(
450
+ {Axis.Q: origin}, [{Axis.Q: target[0]} for target in targets]
451
+ )
450
452
  )
451
453
  await asyncio.sleep(0)
452
454
 
@@ -536,10 +536,10 @@ def create_gripper_jaw_hold_group(encoder_position_um: int) -> MoveGroup:
536
536
  return move_group
537
537
 
538
538
 
539
- def moving_pipettes_in_move_group(group: MoveGroup) -> List[NodeId]:
539
+ def moving_pipettes_in_move_group(
540
+ all_nodes: Set[NodeId], moving_nodes: Set[NodeId]
541
+ ) -> List[NodeId]:
540
542
  """Utility function to get which pipette nodes are moving either in z or their plunger."""
541
- all_nodes = [node for step in group for node, _ in step.items()]
542
- moving_nodes = moving_axes_in_move_group(group)
543
543
  pipettes_moving: List[NodeId] = [
544
544
  k for k in moving_nodes if k in [NodeId.pipette_left, NodeId.pipette_right]
545
545
  ]
@@ -550,16 +550,6 @@ def moving_pipettes_in_move_group(group: MoveGroup) -> List[NodeId]:
550
550
  return pipettes_moving
551
551
 
552
552
 
553
- def moving_axes_in_move_group(group: MoveGroup) -> Set[NodeId]:
554
- """Utility function to get only the moving nodes in a move group."""
555
- ret: Set[NodeId] = set()
556
- for step in group:
557
- for node, node_step in step.items():
558
- if node_step.is_moving_step():
559
- ret.add(node)
560
- return ret
561
-
562
-
563
553
  AxisMapPayload = TypeVar("AxisMapPayload")
564
554
 
565
555
 
@@ -101,6 +101,8 @@ class PipetteDict(InstrumentDict):
101
101
  pipette_bounding_box_offsets: PipetteBoundingBoxOffsetDefinition
102
102
  current_nozzle_map: NozzleMap
103
103
  lld_settings: Optional[Dict[str, Dict[str, float]]]
104
+ plunger_positions: Dict[str, float]
105
+ shaft_ul_per_mm: float
104
106
  available_sensors: AvailableSensorDefinition
105
107
 
106
108
 
@@ -45,6 +45,7 @@ class HeaterShakerEmulator(AbstractEmulator):
45
45
  GCODE.HOME.value: self._home,
46
46
  GCODE.ENTER_BOOTLOADER.value: self._enter_bootloader,
47
47
  GCODE.GET_VERSION.value: self._get_version,
48
+ GCODE.GET_RESET_REASON.value: self._get_reset_reason,
48
49
  GCODE.OPEN_LABWARE_LATCH.value: self._open_labware_latch,
49
50
  GCODE.CLOSE_LABWARE_LATCH.value: self._close_labware_latch,
50
51
  GCODE.GET_LABWARE_LATCH_STATE.value: self._get_labware_latch_state,
@@ -126,6 +127,9 @@ class HeaterShakerEmulator(AbstractEmulator):
126
127
  f"SerialNo:{self._settings.serial_number}"
127
128
  )
128
129
 
130
+ def _get_reset_reason(self, command: Command) -> str:
131
+ return "M114 Last Reset Reason: 01"
132
+
129
133
  def _open_labware_latch(self, command: Command) -> str:
130
134
  self._latch_status = HeaterShakerLabwareLatchStatus.IDLE_OPEN
131
135
  return "M242"
@@ -66,7 +66,7 @@ class ModuleStatusClient:
66
66
  """Read a message from the module server."""
67
67
  try:
68
68
  b = await self._reader.readuntil(MessageDelimiter)
69
- m: Message = Message.parse_raw(b)
69
+ m: Message = Message.model_validate_json(b)
70
70
  return m
71
71
  except LimitOverrunError as e:
72
72
  raise ModuleServerClientError(str(e))