opentrons 8.6.0__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 (601) hide show
  1. opentrons/__init__.py +150 -0
  2. opentrons/_version.py +34 -0
  3. opentrons/calibration_storage/__init__.py +54 -0
  4. opentrons/calibration_storage/deck_configuration.py +62 -0
  5. opentrons/calibration_storage/encoder_decoder.py +31 -0
  6. opentrons/calibration_storage/file_operators.py +142 -0
  7. opentrons/calibration_storage/helpers.py +103 -0
  8. opentrons/calibration_storage/ot2/__init__.py +34 -0
  9. opentrons/calibration_storage/ot2/deck_attitude.py +85 -0
  10. opentrons/calibration_storage/ot2/mark_bad_calibration.py +27 -0
  11. opentrons/calibration_storage/ot2/models/__init__.py +0 -0
  12. opentrons/calibration_storage/ot2/models/v1.py +149 -0
  13. opentrons/calibration_storage/ot2/pipette_offset.py +129 -0
  14. opentrons/calibration_storage/ot2/tip_length.py +281 -0
  15. opentrons/calibration_storage/ot3/__init__.py +31 -0
  16. opentrons/calibration_storage/ot3/deck_attitude.py +83 -0
  17. opentrons/calibration_storage/ot3/gripper_offset.py +156 -0
  18. opentrons/calibration_storage/ot3/models/__init__.py +0 -0
  19. opentrons/calibration_storage/ot3/models/v1.py +122 -0
  20. opentrons/calibration_storage/ot3/module_offset.py +138 -0
  21. opentrons/calibration_storage/ot3/pipette_offset.py +95 -0
  22. opentrons/calibration_storage/types.py +45 -0
  23. opentrons/cli/__init__.py +21 -0
  24. opentrons/cli/__main__.py +5 -0
  25. opentrons/cli/analyze.py +557 -0
  26. opentrons/config/__init__.py +631 -0
  27. opentrons/config/advanced_settings.py +871 -0
  28. opentrons/config/defaults_ot2.py +214 -0
  29. opentrons/config/defaults_ot3.py +499 -0
  30. opentrons/config/feature_flags.py +86 -0
  31. opentrons/config/gripper_config.py +55 -0
  32. opentrons/config/reset.py +203 -0
  33. opentrons/config/robot_configs.py +187 -0
  34. opentrons/config/types.py +183 -0
  35. opentrons/drivers/__init__.py +0 -0
  36. opentrons/drivers/absorbance_reader/__init__.py +11 -0
  37. opentrons/drivers/absorbance_reader/abstract.py +72 -0
  38. opentrons/drivers/absorbance_reader/async_byonoy.py +352 -0
  39. opentrons/drivers/absorbance_reader/driver.py +81 -0
  40. opentrons/drivers/absorbance_reader/hid_protocol.py +161 -0
  41. opentrons/drivers/absorbance_reader/simulator.py +84 -0
  42. opentrons/drivers/asyncio/__init__.py +0 -0
  43. opentrons/drivers/asyncio/communication/__init__.py +22 -0
  44. opentrons/drivers/asyncio/communication/async_serial.py +187 -0
  45. opentrons/drivers/asyncio/communication/errors.py +88 -0
  46. opentrons/drivers/asyncio/communication/serial_connection.py +557 -0
  47. opentrons/drivers/command_builder.py +102 -0
  48. opentrons/drivers/flex_stacker/__init__.py +13 -0
  49. opentrons/drivers/flex_stacker/abstract.py +214 -0
  50. opentrons/drivers/flex_stacker/driver.py +768 -0
  51. opentrons/drivers/flex_stacker/errors.py +68 -0
  52. opentrons/drivers/flex_stacker/simulator.py +309 -0
  53. opentrons/drivers/flex_stacker/types.py +367 -0
  54. opentrons/drivers/flex_stacker/utils.py +19 -0
  55. opentrons/drivers/heater_shaker/__init__.py +5 -0
  56. opentrons/drivers/heater_shaker/abstract.py +76 -0
  57. opentrons/drivers/heater_shaker/driver.py +204 -0
  58. opentrons/drivers/heater_shaker/simulator.py +94 -0
  59. opentrons/drivers/mag_deck/__init__.py +6 -0
  60. opentrons/drivers/mag_deck/abstract.py +44 -0
  61. opentrons/drivers/mag_deck/driver.py +208 -0
  62. opentrons/drivers/mag_deck/simulator.py +63 -0
  63. opentrons/drivers/rpi_drivers/__init__.py +33 -0
  64. opentrons/drivers/rpi_drivers/dev_types.py +94 -0
  65. opentrons/drivers/rpi_drivers/gpio.py +282 -0
  66. opentrons/drivers/rpi_drivers/gpio_simulator.py +127 -0
  67. opentrons/drivers/rpi_drivers/interfaces.py +15 -0
  68. opentrons/drivers/rpi_drivers/types.py +364 -0
  69. opentrons/drivers/rpi_drivers/usb.py +102 -0
  70. opentrons/drivers/rpi_drivers/usb_simulator.py +22 -0
  71. opentrons/drivers/serial_communication.py +151 -0
  72. opentrons/drivers/smoothie_drivers/__init__.py +4 -0
  73. opentrons/drivers/smoothie_drivers/connection.py +51 -0
  74. opentrons/drivers/smoothie_drivers/constants.py +121 -0
  75. opentrons/drivers/smoothie_drivers/driver_3_0.py +1933 -0
  76. opentrons/drivers/smoothie_drivers/errors.py +49 -0
  77. opentrons/drivers/smoothie_drivers/parse_utils.py +143 -0
  78. opentrons/drivers/smoothie_drivers/simulator.py +99 -0
  79. opentrons/drivers/smoothie_drivers/types.py +16 -0
  80. opentrons/drivers/temp_deck/__init__.py +10 -0
  81. opentrons/drivers/temp_deck/abstract.py +54 -0
  82. opentrons/drivers/temp_deck/driver.py +197 -0
  83. opentrons/drivers/temp_deck/simulator.py +57 -0
  84. opentrons/drivers/thermocycler/__init__.py +12 -0
  85. opentrons/drivers/thermocycler/abstract.py +99 -0
  86. opentrons/drivers/thermocycler/driver.py +395 -0
  87. opentrons/drivers/thermocycler/simulator.py +126 -0
  88. opentrons/drivers/types.py +107 -0
  89. opentrons/drivers/utils.py +222 -0
  90. opentrons/execute.py +742 -0
  91. opentrons/hardware_control/__init__.py +65 -0
  92. opentrons/hardware_control/__main__.py +77 -0
  93. opentrons/hardware_control/adapters.py +98 -0
  94. opentrons/hardware_control/api.py +1347 -0
  95. opentrons/hardware_control/backends/__init__.py +7 -0
  96. opentrons/hardware_control/backends/controller.py +400 -0
  97. opentrons/hardware_control/backends/errors.py +9 -0
  98. opentrons/hardware_control/backends/estop_state.py +164 -0
  99. opentrons/hardware_control/backends/flex_protocol.py +497 -0
  100. opentrons/hardware_control/backends/ot3controller.py +1930 -0
  101. opentrons/hardware_control/backends/ot3simulator.py +900 -0
  102. opentrons/hardware_control/backends/ot3utils.py +664 -0
  103. opentrons/hardware_control/backends/simulator.py +442 -0
  104. opentrons/hardware_control/backends/status_bar_state.py +240 -0
  105. opentrons/hardware_control/backends/subsystem_manager.py +431 -0
  106. opentrons/hardware_control/backends/tip_presence_manager.py +173 -0
  107. opentrons/hardware_control/backends/types.py +14 -0
  108. opentrons/hardware_control/constants.py +6 -0
  109. opentrons/hardware_control/dev_types.py +125 -0
  110. opentrons/hardware_control/emulation/__init__.py +0 -0
  111. opentrons/hardware_control/emulation/abstract_emulator.py +21 -0
  112. opentrons/hardware_control/emulation/app.py +56 -0
  113. opentrons/hardware_control/emulation/connection_handler.py +38 -0
  114. opentrons/hardware_control/emulation/heater_shaker.py +150 -0
  115. opentrons/hardware_control/emulation/magdeck.py +60 -0
  116. opentrons/hardware_control/emulation/module_server/__init__.py +8 -0
  117. opentrons/hardware_control/emulation/module_server/client.py +78 -0
  118. opentrons/hardware_control/emulation/module_server/helpers.py +130 -0
  119. opentrons/hardware_control/emulation/module_server/models.py +31 -0
  120. opentrons/hardware_control/emulation/module_server/server.py +110 -0
  121. opentrons/hardware_control/emulation/parser.py +74 -0
  122. opentrons/hardware_control/emulation/proxy.py +241 -0
  123. opentrons/hardware_control/emulation/run_emulator.py +68 -0
  124. opentrons/hardware_control/emulation/scripts/__init__.py +0 -0
  125. opentrons/hardware_control/emulation/scripts/run_app.py +54 -0
  126. opentrons/hardware_control/emulation/scripts/run_module_emulator.py +72 -0
  127. opentrons/hardware_control/emulation/scripts/run_smoothie.py +37 -0
  128. opentrons/hardware_control/emulation/settings.py +119 -0
  129. opentrons/hardware_control/emulation/simulations.py +133 -0
  130. opentrons/hardware_control/emulation/smoothie.py +192 -0
  131. opentrons/hardware_control/emulation/tempdeck.py +69 -0
  132. opentrons/hardware_control/emulation/thermocycler.py +128 -0
  133. opentrons/hardware_control/emulation/types.py +10 -0
  134. opentrons/hardware_control/emulation/util.py +38 -0
  135. opentrons/hardware_control/errors.py +43 -0
  136. opentrons/hardware_control/execution_manager.py +164 -0
  137. opentrons/hardware_control/instruments/__init__.py +5 -0
  138. opentrons/hardware_control/instruments/instrument_abc.py +39 -0
  139. opentrons/hardware_control/instruments/ot2/__init__.py +0 -0
  140. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +152 -0
  141. opentrons/hardware_control/instruments/ot2/pipette.py +777 -0
  142. opentrons/hardware_control/instruments/ot2/pipette_handler.py +995 -0
  143. opentrons/hardware_control/instruments/ot3/__init__.py +0 -0
  144. opentrons/hardware_control/instruments/ot3/gripper.py +420 -0
  145. opentrons/hardware_control/instruments/ot3/gripper_handler.py +173 -0
  146. opentrons/hardware_control/instruments/ot3/instrument_calibration.py +214 -0
  147. opentrons/hardware_control/instruments/ot3/pipette.py +858 -0
  148. opentrons/hardware_control/instruments/ot3/pipette_handler.py +1030 -0
  149. opentrons/hardware_control/module_control.py +332 -0
  150. opentrons/hardware_control/modules/__init__.py +69 -0
  151. opentrons/hardware_control/modules/absorbance_reader.py +373 -0
  152. opentrons/hardware_control/modules/errors.py +7 -0
  153. opentrons/hardware_control/modules/flex_stacker.py +948 -0
  154. opentrons/hardware_control/modules/heater_shaker.py +426 -0
  155. opentrons/hardware_control/modules/lid_temp_status.py +35 -0
  156. opentrons/hardware_control/modules/magdeck.py +233 -0
  157. opentrons/hardware_control/modules/mod_abc.py +245 -0
  158. opentrons/hardware_control/modules/module_calibration.py +93 -0
  159. opentrons/hardware_control/modules/plate_temp_status.py +61 -0
  160. opentrons/hardware_control/modules/tempdeck.py +299 -0
  161. opentrons/hardware_control/modules/thermocycler.py +731 -0
  162. opentrons/hardware_control/modules/types.py +417 -0
  163. opentrons/hardware_control/modules/update.py +255 -0
  164. opentrons/hardware_control/modules/utils.py +73 -0
  165. opentrons/hardware_control/motion_utilities.py +318 -0
  166. opentrons/hardware_control/nozzle_manager.py +422 -0
  167. opentrons/hardware_control/ot3_calibration.py +1171 -0
  168. opentrons/hardware_control/ot3api.py +3227 -0
  169. opentrons/hardware_control/pause_manager.py +31 -0
  170. opentrons/hardware_control/poller.py +112 -0
  171. opentrons/hardware_control/protocols/__init__.py +106 -0
  172. opentrons/hardware_control/protocols/asyncio_configurable.py +11 -0
  173. opentrons/hardware_control/protocols/calibratable.py +45 -0
  174. opentrons/hardware_control/protocols/chassis_accessory_manager.py +90 -0
  175. opentrons/hardware_control/protocols/configurable.py +48 -0
  176. opentrons/hardware_control/protocols/event_sourcer.py +18 -0
  177. opentrons/hardware_control/protocols/execution_controllable.py +33 -0
  178. opentrons/hardware_control/protocols/flex_calibratable.py +96 -0
  179. opentrons/hardware_control/protocols/flex_instrument_configurer.py +52 -0
  180. opentrons/hardware_control/protocols/gripper_controller.py +55 -0
  181. opentrons/hardware_control/protocols/hardware_manager.py +51 -0
  182. opentrons/hardware_control/protocols/identifiable.py +16 -0
  183. opentrons/hardware_control/protocols/instrument_configurer.py +206 -0
  184. opentrons/hardware_control/protocols/liquid_handler.py +266 -0
  185. opentrons/hardware_control/protocols/module_provider.py +16 -0
  186. opentrons/hardware_control/protocols/motion_controller.py +243 -0
  187. opentrons/hardware_control/protocols/position_estimator.py +45 -0
  188. opentrons/hardware_control/protocols/simulatable.py +10 -0
  189. opentrons/hardware_control/protocols/stoppable.py +9 -0
  190. opentrons/hardware_control/protocols/types.py +27 -0
  191. opentrons/hardware_control/robot_calibration.py +224 -0
  192. opentrons/hardware_control/scripts/README.md +28 -0
  193. opentrons/hardware_control/scripts/__init__.py +1 -0
  194. opentrons/hardware_control/scripts/gripper_control.py +208 -0
  195. opentrons/hardware_control/scripts/ot3gripper +7 -0
  196. opentrons/hardware_control/scripts/ot3repl +7 -0
  197. opentrons/hardware_control/scripts/repl.py +187 -0
  198. opentrons/hardware_control/scripts/tc_control.py +97 -0
  199. opentrons/hardware_control/scripts/update_module_fw.py +274 -0
  200. opentrons/hardware_control/simulator_setup.py +260 -0
  201. opentrons/hardware_control/thread_manager.py +431 -0
  202. opentrons/hardware_control/threaded_async_lock.py +97 -0
  203. opentrons/hardware_control/types.py +792 -0
  204. opentrons/hardware_control/util.py +234 -0
  205. opentrons/legacy_broker.py +53 -0
  206. opentrons/legacy_commands/__init__.py +1 -0
  207. opentrons/legacy_commands/commands.py +483 -0
  208. opentrons/legacy_commands/helpers.py +153 -0
  209. opentrons/legacy_commands/module_commands.py +276 -0
  210. opentrons/legacy_commands/protocol_commands.py +54 -0
  211. opentrons/legacy_commands/publisher.py +155 -0
  212. opentrons/legacy_commands/robot_commands.py +51 -0
  213. opentrons/legacy_commands/types.py +1186 -0
  214. opentrons/motion_planning/__init__.py +32 -0
  215. opentrons/motion_planning/adjacent_slots_getters.py +168 -0
  216. opentrons/motion_planning/deck_conflict.py +501 -0
  217. opentrons/motion_planning/errors.py +35 -0
  218. opentrons/motion_planning/types.py +42 -0
  219. opentrons/motion_planning/waypoints.py +218 -0
  220. opentrons/ordered_set.py +138 -0
  221. opentrons/protocol_api/__init__.py +105 -0
  222. opentrons/protocol_api/_liquid.py +157 -0
  223. opentrons/protocol_api/_liquid_properties.py +814 -0
  224. opentrons/protocol_api/_nozzle_layout.py +31 -0
  225. opentrons/protocol_api/_parameter_context.py +300 -0
  226. opentrons/protocol_api/_parameters.py +31 -0
  227. opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
  228. opentrons/protocol_api/_types.py +43 -0
  229. opentrons/protocol_api/config.py +23 -0
  230. opentrons/protocol_api/core/__init__.py +23 -0
  231. opentrons/protocol_api/core/common.py +33 -0
  232. opentrons/protocol_api/core/core_map.py +74 -0
  233. opentrons/protocol_api/core/engine/__init__.py +22 -0
  234. opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
  235. opentrons/protocol_api/core/engine/deck_conflict.py +400 -0
  236. opentrons/protocol_api/core/engine/exceptions.py +19 -0
  237. opentrons/protocol_api/core/engine/instrument.py +2391 -0
  238. opentrons/protocol_api/core/engine/labware.py +238 -0
  239. opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
  240. opentrons/protocol_api/core/engine/module_core.py +1027 -0
  241. opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
  242. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
  243. opentrons/protocol_api/core/engine/point_calculations.py +64 -0
  244. opentrons/protocol_api/core/engine/protocol.py +1153 -0
  245. opentrons/protocol_api/core/engine/robot.py +139 -0
  246. opentrons/protocol_api/core/engine/stringify.py +74 -0
  247. opentrons/protocol_api/core/engine/transfer_components_executor.py +1006 -0
  248. opentrons/protocol_api/core/engine/well.py +241 -0
  249. opentrons/protocol_api/core/instrument.py +459 -0
  250. opentrons/protocol_api/core/labware.py +151 -0
  251. opentrons/protocol_api/core/legacy/__init__.py +11 -0
  252. opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
  253. opentrons/protocol_api/core/legacy/deck.py +369 -0
  254. opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
  255. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
  256. opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
  257. opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
  258. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
  259. opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
  260. opentrons/protocol_api/core/legacy/load_info.py +67 -0
  261. opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
  262. opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
  263. opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
  264. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
  265. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
  266. opentrons/protocol_api/core/module.py +484 -0
  267. opentrons/protocol_api/core/protocol.py +311 -0
  268. opentrons/protocol_api/core/robot.py +51 -0
  269. opentrons/protocol_api/core/well.py +116 -0
  270. opentrons/protocol_api/core/well_grid.py +45 -0
  271. opentrons/protocol_api/create_protocol_context.py +177 -0
  272. opentrons/protocol_api/deck.py +223 -0
  273. opentrons/protocol_api/disposal_locations.py +244 -0
  274. opentrons/protocol_api/instrument_context.py +3272 -0
  275. opentrons/protocol_api/labware.py +1579 -0
  276. opentrons/protocol_api/module_contexts.py +1447 -0
  277. opentrons/protocol_api/module_validation_and_errors.py +61 -0
  278. opentrons/protocol_api/protocol_context.py +1688 -0
  279. opentrons/protocol_api/robot_context.py +303 -0
  280. opentrons/protocol_api/validation.py +761 -0
  281. opentrons/protocol_engine/__init__.py +155 -0
  282. opentrons/protocol_engine/actions/__init__.py +65 -0
  283. opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
  284. opentrons/protocol_engine/actions/action_handler.py +13 -0
  285. opentrons/protocol_engine/actions/actions.py +302 -0
  286. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  287. opentrons/protocol_engine/clients/__init__.py +5 -0
  288. opentrons/protocol_engine/clients/sync_client.py +174 -0
  289. opentrons/protocol_engine/clients/transports.py +197 -0
  290. opentrons/protocol_engine/commands/__init__.py +757 -0
  291. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
  292. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
  293. opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
  294. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
  295. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
  296. opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
  297. opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
  298. opentrons/protocol_engine/commands/aspirate.py +244 -0
  299. opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
  300. opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
  301. opentrons/protocol_engine/commands/blow_out.py +146 -0
  302. opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
  303. opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
  304. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
  305. opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
  306. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
  307. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
  308. opentrons/protocol_engine/commands/command.py +308 -0
  309. opentrons/protocol_engine/commands/command_unions.py +974 -0
  310. opentrons/protocol_engine/commands/comment.py +57 -0
  311. opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
  312. opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
  313. opentrons/protocol_engine/commands/custom.py +67 -0
  314. opentrons/protocol_engine/commands/dispense.py +194 -0
  315. opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
  316. opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
  317. opentrons/protocol_engine/commands/drop_tip.py +232 -0
  318. opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
  319. opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
  320. opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
  321. opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
  322. opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
  323. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
  324. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
  325. opentrons/protocol_engine/commands/flex_stacker/store.py +339 -0
  326. opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
  327. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  328. opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
  329. opentrons/protocol_engine/commands/hash_command_params.py +38 -0
  330. opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
  331. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
  332. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
  333. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
  334. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
  335. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
  336. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
  337. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
  338. opentrons/protocol_engine/commands/home.py +100 -0
  339. opentrons/protocol_engine/commands/identify_module.py +86 -0
  340. opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
  341. opentrons/protocol_engine/commands/liquid_probe.py +464 -0
  342. opentrons/protocol_engine/commands/load_labware.py +210 -0
  343. opentrons/protocol_engine/commands/load_lid.py +154 -0
  344. opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
  345. opentrons/protocol_engine/commands/load_liquid.py +95 -0
  346. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  347. opentrons/protocol_engine/commands/load_module.py +223 -0
  348. opentrons/protocol_engine/commands/load_pipette.py +167 -0
  349. opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
  350. opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
  351. opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
  352. opentrons/protocol_engine/commands/move_labware.py +546 -0
  353. opentrons/protocol_engine/commands/move_relative.py +102 -0
  354. opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
  355. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
  356. opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
  357. opentrons/protocol_engine/commands/move_to_well.py +119 -0
  358. opentrons/protocol_engine/commands/movement_common.py +338 -0
  359. opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
  360. opentrons/protocol_engine/commands/pipetting_common.py +443 -0
  361. opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
  362. opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
  363. opentrons/protocol_engine/commands/reload_labware.py +90 -0
  364. opentrons/protocol_engine/commands/retract_axis.py +75 -0
  365. opentrons/protocol_engine/commands/robot/__init__.py +70 -0
  366. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -0
  367. opentrons/protocol_engine/commands/robot/common.py +18 -0
  368. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  369. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  370. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  371. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +86 -0
  372. opentrons/protocol_engine/commands/save_position.py +109 -0
  373. opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
  374. opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
  375. opentrons/protocol_engine/commands/set_status_bar.py +89 -0
  376. opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
  377. opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
  378. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
  379. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
  380. opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
  381. opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
  382. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
  383. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
  384. opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
  385. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
  386. opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
  387. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
  388. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
  389. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
  390. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
  391. opentrons/protocol_engine/commands/touch_tip.py +189 -0
  392. opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
  393. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
  394. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
  395. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
  396. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
  397. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
  398. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
  399. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
  400. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
  401. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
  402. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
  403. opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
  404. opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
  405. opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
  406. opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
  407. opentrons/protocol_engine/create_protocol_engine.py +193 -0
  408. opentrons/protocol_engine/engine_support.py +28 -0
  409. opentrons/protocol_engine/error_recovery_policy.py +81 -0
  410. opentrons/protocol_engine/errors/__init__.py +191 -0
  411. opentrons/protocol_engine/errors/error_occurrence.py +182 -0
  412. opentrons/protocol_engine/errors/exceptions.py +1308 -0
  413. opentrons/protocol_engine/execution/__init__.py +50 -0
  414. opentrons/protocol_engine/execution/command_executor.py +216 -0
  415. opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
  416. opentrons/protocol_engine/execution/door_watcher.py +119 -0
  417. opentrons/protocol_engine/execution/equipment.py +819 -0
  418. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  419. opentrons/protocol_engine/execution/gantry_mover.py +686 -0
  420. opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
  421. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
  422. opentrons/protocol_engine/execution/labware_movement.py +297 -0
  423. opentrons/protocol_engine/execution/movement.py +350 -0
  424. opentrons/protocol_engine/execution/pipetting.py +607 -0
  425. opentrons/protocol_engine/execution/queue_worker.py +86 -0
  426. opentrons/protocol_engine/execution/rail_lights.py +25 -0
  427. opentrons/protocol_engine/execution/run_control.py +33 -0
  428. opentrons/protocol_engine/execution/status_bar.py +34 -0
  429. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
  430. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
  431. opentrons/protocol_engine/execution/tip_handler.py +550 -0
  432. opentrons/protocol_engine/labware_offset_standardization.py +194 -0
  433. opentrons/protocol_engine/notes/__init__.py +17 -0
  434. opentrons/protocol_engine/notes/notes.py +59 -0
  435. opentrons/protocol_engine/plugins.py +104 -0
  436. opentrons/protocol_engine/protocol_engine.py +683 -0
  437. opentrons/protocol_engine/resources/__init__.py +26 -0
  438. opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
  439. opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
  440. opentrons/protocol_engine/resources/file_provider.py +161 -0
  441. opentrons/protocol_engine/resources/fixture_validation.py +68 -0
  442. opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
  443. opentrons/protocol_engine/resources/labware_validation.py +73 -0
  444. opentrons/protocol_engine/resources/model_utils.py +32 -0
  445. opentrons/protocol_engine/resources/module_data_provider.py +44 -0
  446. opentrons/protocol_engine/resources/ot3_validation.py +21 -0
  447. opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
  448. opentrons/protocol_engine/slot_standardization.py +128 -0
  449. opentrons/protocol_engine/state/__init__.py +1 -0
  450. opentrons/protocol_engine/state/_abstract_store.py +27 -0
  451. opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
  452. opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
  453. opentrons/protocol_engine/state/_move_types.py +83 -0
  454. opentrons/protocol_engine/state/_well_math.py +193 -0
  455. opentrons/protocol_engine/state/addressable_areas.py +699 -0
  456. opentrons/protocol_engine/state/command_history.py +309 -0
  457. opentrons/protocol_engine/state/commands.py +1164 -0
  458. opentrons/protocol_engine/state/config.py +39 -0
  459. opentrons/protocol_engine/state/files.py +57 -0
  460. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  461. opentrons/protocol_engine/state/geometry.py +2408 -0
  462. opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
  463. opentrons/protocol_engine/state/labware.py +1432 -0
  464. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  465. opentrons/protocol_engine/state/liquids.py +73 -0
  466. opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
  467. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
  468. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
  469. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
  470. opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
  471. opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
  472. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
  473. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
  474. opentrons/protocol_engine/state/modules.py +1515 -0
  475. opentrons/protocol_engine/state/motion.py +373 -0
  476. opentrons/protocol_engine/state/pipettes.py +905 -0
  477. opentrons/protocol_engine/state/state.py +421 -0
  478. opentrons/protocol_engine/state/state_summary.py +36 -0
  479. opentrons/protocol_engine/state/tips.py +420 -0
  480. opentrons/protocol_engine/state/update_types.py +904 -0
  481. opentrons/protocol_engine/state/wells.py +290 -0
  482. opentrons/protocol_engine/types/__init__.py +310 -0
  483. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  484. opentrons/protocol_engine/types/command_annotations.py +53 -0
  485. opentrons/protocol_engine/types/deck_configuration.py +81 -0
  486. opentrons/protocol_engine/types/execution.py +96 -0
  487. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  488. opentrons/protocol_engine/types/instrument.py +47 -0
  489. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  490. opentrons/protocol_engine/types/labware.py +131 -0
  491. opentrons/protocol_engine/types/labware_movement.py +22 -0
  492. opentrons/protocol_engine/types/labware_offset_location.py +111 -0
  493. opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
  494. opentrons/protocol_engine/types/liquid.py +40 -0
  495. opentrons/protocol_engine/types/liquid_class.py +59 -0
  496. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  497. opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
  498. opentrons/protocol_engine/types/location.py +194 -0
  499. opentrons/protocol_engine/types/module.py +310 -0
  500. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  501. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  502. opentrons/protocol_engine/types/tip.py +18 -0
  503. opentrons/protocol_engine/types/util.py +21 -0
  504. opentrons/protocol_engine/types/well_position.py +124 -0
  505. opentrons/protocol_reader/__init__.py +37 -0
  506. opentrons/protocol_reader/extract_labware_definitions.py +66 -0
  507. opentrons/protocol_reader/file_format_validator.py +152 -0
  508. opentrons/protocol_reader/file_hasher.py +27 -0
  509. opentrons/protocol_reader/file_identifier.py +284 -0
  510. opentrons/protocol_reader/file_reader_writer.py +90 -0
  511. opentrons/protocol_reader/input_file.py +16 -0
  512. opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
  513. opentrons/protocol_reader/protocol_reader.py +188 -0
  514. opentrons/protocol_reader/protocol_source.py +124 -0
  515. opentrons/protocol_reader/role_analyzer.py +86 -0
  516. opentrons/protocol_runner/__init__.py +26 -0
  517. opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
  518. opentrons/protocol_runner/json_file_reader.py +55 -0
  519. opentrons/protocol_runner/json_translator.py +314 -0
  520. opentrons/protocol_runner/legacy_command_mapper.py +852 -0
  521. opentrons/protocol_runner/legacy_context_plugin.py +116 -0
  522. opentrons/protocol_runner/protocol_runner.py +530 -0
  523. opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
  524. opentrons/protocol_runner/run_orchestrator.py +496 -0
  525. opentrons/protocol_runner/task_queue.py +95 -0
  526. opentrons/protocols/__init__.py +6 -0
  527. opentrons/protocols/advanced_control/__init__.py +0 -0
  528. opentrons/protocols/advanced_control/common.py +38 -0
  529. opentrons/protocols/advanced_control/mix.py +60 -0
  530. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  531. opentrons/protocols/advanced_control/transfers/common.py +180 -0
  532. opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
  533. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
  534. opentrons/protocols/api_support/__init__.py +0 -0
  535. opentrons/protocols/api_support/constants.py +8 -0
  536. opentrons/protocols/api_support/deck_type.py +110 -0
  537. opentrons/protocols/api_support/definitions.py +18 -0
  538. opentrons/protocols/api_support/instrument.py +151 -0
  539. opentrons/protocols/api_support/labware_like.py +233 -0
  540. opentrons/protocols/api_support/tip_tracker.py +175 -0
  541. opentrons/protocols/api_support/types.py +32 -0
  542. opentrons/protocols/api_support/util.py +403 -0
  543. opentrons/protocols/bundle.py +89 -0
  544. opentrons/protocols/duration/__init__.py +4 -0
  545. opentrons/protocols/duration/errors.py +5 -0
  546. opentrons/protocols/duration/estimator.py +628 -0
  547. opentrons/protocols/execution/__init__.py +0 -0
  548. opentrons/protocols/execution/dev_types.py +181 -0
  549. opentrons/protocols/execution/errors.py +40 -0
  550. opentrons/protocols/execution/execute.py +84 -0
  551. opentrons/protocols/execution/execute_json_v3.py +275 -0
  552. opentrons/protocols/execution/execute_json_v4.py +359 -0
  553. opentrons/protocols/execution/execute_json_v5.py +28 -0
  554. opentrons/protocols/execution/execute_python.py +169 -0
  555. opentrons/protocols/execution/json_dispatchers.py +87 -0
  556. opentrons/protocols/execution/types.py +7 -0
  557. opentrons/protocols/geometry/__init__.py +0 -0
  558. opentrons/protocols/geometry/planning.py +297 -0
  559. opentrons/protocols/labware.py +312 -0
  560. opentrons/protocols/models/__init__.py +0 -0
  561. opentrons/protocols/models/json_protocol.py +679 -0
  562. opentrons/protocols/parameters/__init__.py +0 -0
  563. opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
  564. opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
  565. opentrons/protocols/parameters/exceptions.py +34 -0
  566. opentrons/protocols/parameters/parameter_definition.py +272 -0
  567. opentrons/protocols/parameters/types.py +17 -0
  568. opentrons/protocols/parameters/validation.py +267 -0
  569. opentrons/protocols/parse.py +671 -0
  570. opentrons/protocols/types.py +159 -0
  571. opentrons/py.typed +0 -0
  572. opentrons/resources/scripts/lpc21isp +0 -0
  573. opentrons/resources/smoothie-edge-8414642.hex +23010 -0
  574. opentrons/simulate.py +1065 -0
  575. opentrons/system/__init__.py +6 -0
  576. opentrons/system/camera.py +51 -0
  577. opentrons/system/log_control.py +59 -0
  578. opentrons/system/nmcli.py +856 -0
  579. opentrons/system/resin.py +24 -0
  580. opentrons/system/smoothie_update.py +15 -0
  581. opentrons/system/wifi.py +204 -0
  582. opentrons/tools/__init__.py +0 -0
  583. opentrons/tools/args_handler.py +22 -0
  584. opentrons/tools/write_pipette_memory.py +157 -0
  585. opentrons/types.py +618 -0
  586. opentrons/util/__init__.py +1 -0
  587. opentrons/util/async_helpers.py +166 -0
  588. opentrons/util/broker.py +84 -0
  589. opentrons/util/change_notifier.py +47 -0
  590. opentrons/util/entrypoint_util.py +278 -0
  591. opentrons/util/get_union_elements.py +26 -0
  592. opentrons/util/helpers.py +6 -0
  593. opentrons/util/linal.py +178 -0
  594. opentrons/util/logging_config.py +265 -0
  595. opentrons/util/logging_queue_handler.py +61 -0
  596. opentrons/util/performance_helpers.py +157 -0
  597. opentrons-8.6.0.dist-info/METADATA +37 -0
  598. opentrons-8.6.0.dist-info/RECORD +601 -0
  599. opentrons-8.6.0.dist-info/WHEEL +4 -0
  600. opentrons-8.6.0.dist-info/entry_points.txt +3 -0
  601. opentrons-8.6.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,2391 @@
1
+ """ProtocolEngine-based InstrumentContext core implementation."""
2
+
3
+ from __future__ import annotations
4
+ from itertools import dropwhile
5
+ from copy import deepcopy
6
+ from typing import (
7
+ Optional,
8
+ TYPE_CHECKING,
9
+ cast,
10
+ Union,
11
+ List,
12
+ Sequence,
13
+ Tuple,
14
+ NamedTuple,
15
+ Literal,
16
+ )
17
+ from opentrons.types import (
18
+ Location,
19
+ Mount,
20
+ NozzleConfigurationType,
21
+ NozzleMapInterface,
22
+ MeniscusTrackingTarget,
23
+ )
24
+ from opentrons.hardware_control import SyncHardwareAPI
25
+ from opentrons.hardware_control.dev_types import PipetteDict
26
+ from opentrons.protocols.api_support.util import FlowRates, find_value_for_api_version
27
+ from opentrons.protocols.api_support.types import APIVersion
28
+ from opentrons.protocols.advanced_control.transfers.common import (
29
+ TransferTipPolicyV2,
30
+ NoLiquidClassPropertyError,
31
+ )
32
+ from opentrons.protocols.advanced_control.transfers import common as tx_commons
33
+ from opentrons.protocols.advanced_control.transfers.transfer_liquid_utils import (
34
+ check_current_volume_before_dispensing,
35
+ )
36
+ from opentrons.protocol_engine import commands as cmd
37
+ from opentrons.protocol_engine import (
38
+ DeckPoint,
39
+ DropTipWellLocation,
40
+ DropTipWellOrigin,
41
+ WellLocation,
42
+ WellOrigin,
43
+ WellOffset,
44
+ AllNozzleLayoutConfiguration,
45
+ SingleNozzleLayoutConfiguration,
46
+ RowNozzleLayoutConfiguration,
47
+ ColumnNozzleLayoutConfiguration,
48
+ QuadrantNozzleLayoutConfiguration,
49
+ )
50
+ from opentrons.protocol_engine.types import (
51
+ PRIMARY_NOZZLE_LITERAL,
52
+ NozzleLayoutConfigurationType,
53
+ AddressableOffsetVector,
54
+ LiquidClassRecord,
55
+ NextTipInfo,
56
+ PickUpTipWellLocation,
57
+ LiquidHandlingWellLocation,
58
+ )
59
+ from opentrons.protocol_engine.types import (
60
+ LiquidTrackingType,
61
+ WellLocationFunction,
62
+ )
63
+ from opentrons.protocol_engine.types.automatic_tip_selection import (
64
+ NoTipAvailable,
65
+ NoTipReason,
66
+ )
67
+ from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError
68
+ from opentrons.protocol_engine.clients import SyncClient as EngineClient
69
+ from opentrons.protocols.api_support.definitions import MAX_SUPPORTED_VERSION
70
+ from opentrons_shared_data.pipette.types import (
71
+ PIPETTE_API_NAMES_MAP,
72
+ LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP,
73
+ )
74
+ from opentrons_shared_data.errors.exceptions import (
75
+ UnsupportedHardwareCommand,
76
+ CommandPreconditionViolated,
77
+ )
78
+ from opentrons_shared_data.liquid_classes.liquid_class_definition import BlowoutLocation
79
+ from opentrons.protocol_api._nozzle_layout import NozzleLayout
80
+ from . import overlap_versions, pipette_movement_conflict
81
+ from . import transfer_components_executor as tx_comps_executor
82
+
83
+ from .well import WellCore
84
+ from .labware import LabwareCore
85
+ from ..instrument import AbstractInstrument
86
+ from ...disposal_locations import TrashBin, WasteChute
87
+
88
+ if TYPE_CHECKING:
89
+ from .protocol import ProtocolCore
90
+ from opentrons.protocol_api._liquid import LiquidClass
91
+ from opentrons.protocol_api._liquid_properties import (
92
+ TransferProperties,
93
+ MultiDispenseProperties,
94
+ SingleDispenseProperties,
95
+ )
96
+
97
+ _DISPENSE_VOLUME_VALIDATION_ADDED_IN = APIVersion(2, 17)
98
+ _RESIN_TIP_DEFAULT_VOLUME = 400
99
+ _RESIN_TIP_DEFAULT_FLOW_RATE = 10.0
100
+
101
+ _FLEX_PIPETTE_NAMES_FIXED_IN = APIVersion(2, 23)
102
+ """The version after which InstrumentContext.name returns the correct API-specific names of Flex pipettes."""
103
+
104
+
105
+ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
106
+ """Instrument API core using a ProtocolEngine.
107
+
108
+ Args:
109
+ pipette_id: ProtocolEngine ID of the loaded instrument.
110
+ """
111
+
112
+ def __init__(
113
+ self,
114
+ pipette_id: str,
115
+ engine_client: EngineClient,
116
+ sync_hardware_api: SyncHardwareAPI,
117
+ protocol_core: ProtocolCore,
118
+ default_movement_speed: float,
119
+ ) -> None:
120
+ self._pipette_id = pipette_id
121
+ self._engine_client = engine_client
122
+ self._sync_hardware_api = sync_hardware_api
123
+ self._protocol_core = protocol_core
124
+
125
+ # TODO(jbl 2022-11-03) flow_rates should not live in the cores, and should be moved to the protocol context
126
+ # along with other rate related refactors (for the hardware API)
127
+ flow_rates = self._engine_client.state.pipettes.get_flow_rates(pipette_id)
128
+ self._aspirate_flow_rate = find_value_for_api_version(
129
+ MAX_SUPPORTED_VERSION, flow_rates.default_aspirate
130
+ )
131
+ self._dispense_flow_rate = find_value_for_api_version(
132
+ MAX_SUPPORTED_VERSION, flow_rates.default_dispense
133
+ )
134
+ self._blow_out_flow_rate = find_value_for_api_version(
135
+ MAX_SUPPORTED_VERSION, flow_rates.default_blow_out
136
+ )
137
+ self._flow_rates = FlowRates(self)
138
+
139
+ self.set_default_speed(speed=default_movement_speed)
140
+ self._liquid_presence_detection = bool(
141
+ self._engine_client.state.pipettes.get_liquid_presence_detection(pipette_id)
142
+ )
143
+ if (
144
+ self._liquid_presence_detection
145
+ and not self._pressure_supported_by_pipette()
146
+ ):
147
+ raise UnsupportedHardwareCommand(
148
+ "Pressure sensor not available for this pipette"
149
+ )
150
+
151
+ @property
152
+ def pipette_id(self) -> str:
153
+ """The instrument's unique ProtocolEngine ID."""
154
+ return self._pipette_id
155
+
156
+ def get_default_speed(self) -> float:
157
+ speed = self._engine_client.state.pipettes.get_movement_speed(
158
+ pipette_id=self._pipette_id
159
+ )
160
+ assert speed is not None, "Pipette loading should have set a default speed."
161
+ return speed
162
+
163
+ def set_default_speed(self, speed: float) -> None:
164
+ self._engine_client.set_pipette_movement_speed(
165
+ pipette_id=self._pipette_id, speed=speed
166
+ )
167
+
168
+ def air_gap_in_place(
169
+ self, volume: float, flow_rate: float, correction_volume: Optional[float] = None
170
+ ) -> None:
171
+ """Aspirate a given volume of air from the current location of the pipette.
172
+
173
+ Args:
174
+ volume: The volume of air to aspirate, in microliters.
175
+ folw_rate: The flow rate of air into the pipette, in microliters/s
176
+ """
177
+ self._engine_client.execute_command(
178
+ cmd.AirGapInPlaceParams(
179
+ pipetteId=self._pipette_id,
180
+ volume=volume,
181
+ flowRate=flow_rate,
182
+ correctionVolume=correction_volume,
183
+ )
184
+ )
185
+
186
+ def aspirate(
187
+ self,
188
+ location: Location,
189
+ well_core: Optional[WellCore],
190
+ volume: float,
191
+ rate: float,
192
+ flow_rate: float,
193
+ in_place: bool,
194
+ meniscus_tracking: Optional[MeniscusTrackingTarget] = None,
195
+ correction_volume: Optional[float] = None,
196
+ ) -> None:
197
+ """Aspirate a given volume of liquid from the specified location.
198
+ Args:
199
+ volume: The volume of liquid to aspirate, in microliters.
200
+ location: The exact location to aspirate from.
201
+ well_core: The well to aspirate from, if applicable.
202
+ rate: Not used in this core.
203
+ flow_rate: The flow rate in µL/s to aspirate at.
204
+ in_place: whether this is a in-place command.
205
+ meniscus_tracking: Optional data about where to aspirate from.
206
+ """
207
+ if meniscus_tracking == MeniscusTrackingTarget.START:
208
+ raise ValueError("Cannot aspirate at the starting liquid height.")
209
+ if well_core is None:
210
+ if not in_place:
211
+ self._engine_client.execute_command(
212
+ cmd.MoveToCoordinatesParams(
213
+ pipetteId=self._pipette_id,
214
+ coordinates=DeckPoint(
215
+ x=location.point.x, y=location.point.y, z=location.point.z
216
+ ),
217
+ minimumZHeight=None,
218
+ forceDirect=False,
219
+ speed=None,
220
+ )
221
+ )
222
+
223
+ self._engine_client.execute_command(
224
+ cmd.AspirateInPlaceParams(
225
+ pipetteId=self._pipette_id,
226
+ volume=volume,
227
+ flowRate=flow_rate,
228
+ correctionVolume=correction_volume,
229
+ )
230
+ )
231
+
232
+ else:
233
+ well_name = well_core.get_name()
234
+ labware_id = well_core.labware_id
235
+
236
+ (
237
+ well_location,
238
+ dynamic_liquid_tracking,
239
+ ) = self._engine_client.state.geometry.get_relative_well_location(
240
+ labware_id=labware_id,
241
+ well_name=well_name,
242
+ absolute_point=location.point,
243
+ location_type=WellLocationFunction.LIQUID_HANDLING,
244
+ meniscus_tracking=meniscus_tracking,
245
+ )
246
+ pipette_movement_conflict.check_safe_for_pipette_movement(
247
+ engine_state=self._engine_client.state,
248
+ pipette_id=self._pipette_id,
249
+ labware_id=labware_id,
250
+ well_name=well_name,
251
+ well_location=well_location,
252
+ )
253
+ assert isinstance(well_location, LiquidHandlingWellLocation)
254
+ if dynamic_liquid_tracking:
255
+ self._engine_client.execute_command(
256
+ cmd.AspirateWhileTrackingParams(
257
+ pipetteId=self._pipette_id,
258
+ labwareId=labware_id,
259
+ wellName=well_name,
260
+ wellLocation=well_location,
261
+ volume=volume,
262
+ flowRate=flow_rate,
263
+ correctionVolume=correction_volume,
264
+ )
265
+ )
266
+ else:
267
+ self._engine_client.execute_command(
268
+ cmd.AspirateParams(
269
+ pipetteId=self._pipette_id,
270
+ labwareId=labware_id,
271
+ wellName=well_name,
272
+ wellLocation=well_location,
273
+ volume=volume,
274
+ flowRate=flow_rate,
275
+ correctionVolume=correction_volume,
276
+ )
277
+ )
278
+
279
+ self._protocol_core.set_last_location(location=location, mount=self.get_mount())
280
+
281
+ def dispense(
282
+ self,
283
+ location: Union[Location, TrashBin, WasteChute],
284
+ well_core: Optional[WellCore],
285
+ volume: float,
286
+ rate: float,
287
+ flow_rate: float,
288
+ in_place: bool,
289
+ push_out: Optional[float],
290
+ meniscus_tracking: Optional[MeniscusTrackingTarget] = None,
291
+ correction_volume: Optional[float] = None,
292
+ ) -> None:
293
+ """Dispense a given volume of liquid into the specified location.
294
+ Args:
295
+ volume: The volume of liquid to dispense, in microliters.
296
+ location: The exact location to dispense to.
297
+ well_core: The well to dispense to, if applicable.
298
+ rate: Not used in this core.
299
+ flow_rate: The flow rate in µL/s to dispense at.
300
+ in_place: whether this is a in-place command.
301
+ push_out: The amount to push the plunger below bottom position.
302
+ meniscus_tracking: Optional data about where to dispense from.
303
+ """
304
+ if self._protocol_core.api_version < _DISPENSE_VOLUME_VALIDATION_ADDED_IN:
305
+ # In older API versions, when you try to dispense more than you can,
306
+ # it gets clamped.
307
+ volume = min(volume, self.get_current_volume())
308
+ else:
309
+ # Newer API versions raise an error if you try to dispense more than
310
+ # you can. Let the error come from Protocol Engine's validation.
311
+ pass
312
+
313
+ if well_core is None:
314
+ if not in_place:
315
+ if isinstance(location, (TrashBin, WasteChute)):
316
+ self._move_to_disposal_location(
317
+ disposal_location=location, force_direct=False, speed=None
318
+ )
319
+ else:
320
+ self._engine_client.execute_command(
321
+ cmd.MoveToCoordinatesParams(
322
+ pipetteId=self._pipette_id,
323
+ coordinates=DeckPoint(
324
+ x=location.point.x,
325
+ y=location.point.y,
326
+ z=location.point.z,
327
+ ),
328
+ minimumZHeight=None,
329
+ forceDirect=False,
330
+ speed=None,
331
+ )
332
+ )
333
+
334
+ self._engine_client.execute_command(
335
+ cmd.DispenseInPlaceParams(
336
+ pipetteId=self._pipette_id,
337
+ volume=volume,
338
+ flowRate=flow_rate,
339
+ pushOut=push_out,
340
+ correctionVolume=correction_volume,
341
+ )
342
+ )
343
+ else:
344
+ if isinstance(location, (TrashBin, WasteChute)):
345
+ raise ValueError("Trash Bin and Waste Chute have no Wells.")
346
+ well_name = well_core.get_name()
347
+ labware_id = well_core.labware_id
348
+
349
+ (
350
+ well_location,
351
+ dynamic_liquid_tracking,
352
+ ) = self._engine_client.state.geometry.get_relative_well_location(
353
+ labware_id=labware_id,
354
+ well_name=well_name,
355
+ absolute_point=location.point,
356
+ location_type=WellLocationFunction.LIQUID_HANDLING,
357
+ meniscus_tracking=meniscus_tracking,
358
+ )
359
+ assert isinstance(well_location, LiquidHandlingWellLocation)
360
+ pipette_movement_conflict.check_safe_for_pipette_movement(
361
+ engine_state=self._engine_client.state,
362
+ pipette_id=self._pipette_id,
363
+ labware_id=labware_id,
364
+ well_name=well_name,
365
+ well_location=well_location,
366
+ )
367
+ if dynamic_liquid_tracking:
368
+ self._engine_client.execute_command(
369
+ cmd.DispenseWhileTrackingParams(
370
+ pipetteId=self._pipette_id,
371
+ labwareId=labware_id,
372
+ wellName=well_name,
373
+ wellLocation=well_location,
374
+ volume=volume,
375
+ flowRate=flow_rate,
376
+ pushOut=push_out,
377
+ correctionVolume=correction_volume,
378
+ )
379
+ )
380
+ else:
381
+ self._engine_client.execute_command(
382
+ cmd.DispenseParams(
383
+ pipetteId=self._pipette_id,
384
+ labwareId=labware_id,
385
+ wellName=well_name,
386
+ wellLocation=well_location,
387
+ volume=volume,
388
+ flowRate=flow_rate,
389
+ pushOut=push_out,
390
+ correctionVolume=correction_volume,
391
+ )
392
+ )
393
+
394
+ self._protocol_core.set_last_location(location=location, mount=self.get_mount())
395
+
396
+ def blow_out(
397
+ self,
398
+ location: Union[Location, TrashBin, WasteChute],
399
+ well_core: Optional[WellCore],
400
+ in_place: bool,
401
+ ) -> None:
402
+ """Blow liquid out of the tip.
403
+
404
+ Args:
405
+ location: The location to blow out into.
406
+ well_core: The well to blow out into.
407
+ in_place: whether this is a in-place command.
408
+ """
409
+ flow_rate = self.get_blow_out_flow_rate(1.0)
410
+ if well_core is None:
411
+ if not in_place:
412
+ if isinstance(location, (TrashBin, WasteChute)):
413
+ self._move_to_disposal_location(
414
+ disposal_location=location, force_direct=False, speed=None
415
+ )
416
+ else:
417
+ self._engine_client.execute_command(
418
+ cmd.MoveToCoordinatesParams(
419
+ pipetteId=self._pipette_id,
420
+ coordinates=DeckPoint(
421
+ x=location.point.x,
422
+ y=location.point.y,
423
+ z=location.point.z,
424
+ ),
425
+ forceDirect=False,
426
+ minimumZHeight=None,
427
+ speed=None,
428
+ )
429
+ )
430
+
431
+ self._engine_client.execute_command(
432
+ cmd.BlowOutInPlaceParams(pipetteId=self._pipette_id, flowRate=flow_rate)
433
+ )
434
+ else:
435
+ if isinstance(location, (TrashBin, WasteChute)):
436
+ raise ValueError("Trash Bin and Waste Chute have no Wells.")
437
+ well_name = well_core.get_name()
438
+ labware_id = well_core.labware_id
439
+
440
+ (
441
+ well_location,
442
+ _,
443
+ ) = self._engine_client.state.geometry.get_relative_well_location(
444
+ labware_id=labware_id,
445
+ well_name=well_name,
446
+ absolute_point=location.point,
447
+ location_type=WellLocationFunction.BASE,
448
+ )
449
+
450
+ pipette_movement_conflict.check_safe_for_pipette_movement(
451
+ engine_state=self._engine_client.state,
452
+ pipette_id=self._pipette_id,
453
+ labware_id=labware_id,
454
+ well_name=well_name,
455
+ well_location=well_location,
456
+ )
457
+ assert isinstance(well_location, WellLocation)
458
+ self._engine_client.execute_command(
459
+ cmd.BlowOutParams(
460
+ pipetteId=self._pipette_id,
461
+ labwareId=labware_id,
462
+ wellName=well_name,
463
+ wellLocation=well_location,
464
+ # TODO(jbl 2022-11-07) PAPIv2 does not have an argument for rate and
465
+ # this also needs to be refactored along with other flow rate related issues
466
+ flowRate=flow_rate,
467
+ )
468
+ )
469
+
470
+ self._protocol_core.set_last_location(location=location, mount=self.get_mount())
471
+
472
+ def touch_tip(
473
+ self,
474
+ location: Location,
475
+ well_core: WellCore,
476
+ radius: float,
477
+ z_offset: float,
478
+ speed: float,
479
+ mm_from_edge: Optional[float] = None,
480
+ ) -> None:
481
+ """Touch pipette tip to edges of the well
482
+
483
+ Args:
484
+ location: Location moved to, only used for ProtocolCore location cache.
485
+ well_core: The target well for touch tip.
486
+ radius: Percentage modifier for well radius to touch.
487
+ z_offset: Vertical offset for pipette tip during touch tip.
488
+ speed: Speed for the touch tip movements.
489
+ mm_from_edge: Offset from the edge of the well to move to. Requires a radius of 1.
490
+ """
491
+ if mm_from_edge is not None and radius != 1.0:
492
+ raise ValueError("radius must be set to 1.0 if mm_from_edge is provided.")
493
+
494
+ well_name = well_core.get_name()
495
+ labware_id = well_core.labware_id
496
+
497
+ # Touch tip is always done from the top of the well.
498
+ well_location = WellLocation(
499
+ origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=z_offset)
500
+ )
501
+ pipette_movement_conflict.check_safe_for_pipette_movement(
502
+ engine_state=self._engine_client.state,
503
+ pipette_id=self._pipette_id,
504
+ labware_id=labware_id,
505
+ well_name=well_name,
506
+ well_location=well_location,
507
+ )
508
+ self._engine_client.execute_command(
509
+ cmd.TouchTipParams(
510
+ pipetteId=self._pipette_id,
511
+ labwareId=labware_id,
512
+ wellName=well_name,
513
+ wellLocation=well_location,
514
+ radius=radius,
515
+ mmFromEdge=mm_from_edge,
516
+ speed=speed,
517
+ )
518
+ )
519
+
520
+ self._protocol_core.set_last_location(location=location, mount=self.get_mount())
521
+
522
+ def pick_up_tip(
523
+ self,
524
+ location: Location,
525
+ well_core: WellCore,
526
+ presses: Optional[int],
527
+ increment: Optional[float],
528
+ prep_after: bool = True,
529
+ ) -> None:
530
+ """Move to and pick up a tip from a given well.
531
+
532
+ Args:
533
+ location: The location of the well we're picking up from.
534
+ Used to calculate the relative well offset for the pick up command.
535
+ well_core: The "well" to pick up from.
536
+ presses: Customize the number of presses the pipette does.
537
+ increment: Customize the movement "distance" of the pipette to press harder.
538
+ prep_after: Not used by this core, pipette preparation will always happen.
539
+ """
540
+ assert (
541
+ presses is None and increment is None
542
+ ), "Tip pick-up with custom presses or increment deprecated"
543
+
544
+ well_name = well_core.get_name()
545
+ labware_id = well_core.labware_id
546
+
547
+ (
548
+ well_location,
549
+ _,
550
+ ) = self._engine_client.state.geometry.get_relative_well_location(
551
+ labware_id=labware_id,
552
+ well_name=well_name,
553
+ absolute_point=location.point,
554
+ location_type=WellLocationFunction.PICK_UP_TIP,
555
+ )
556
+ pipette_movement_conflict.check_safe_for_tip_pickup_and_return(
557
+ engine_state=self._engine_client.state,
558
+ pipette_id=self._pipette_id,
559
+ labware_id=labware_id,
560
+ )
561
+ pipette_movement_conflict.check_safe_for_pipette_movement(
562
+ engine_state=self._engine_client.state,
563
+ pipette_id=self._pipette_id,
564
+ labware_id=labware_id,
565
+ well_name=well_name,
566
+ well_location=well_location,
567
+ )
568
+ assert isinstance(well_location, PickUpTipWellLocation)
569
+ self._engine_client.execute_command(
570
+ cmd.PickUpTipParams(
571
+ pipetteId=self._pipette_id,
572
+ labwareId=labware_id,
573
+ wellName=well_name,
574
+ wellLocation=well_location,
575
+ )
576
+ )
577
+
578
+ # Set the "last location" unconditionally, even if the command failed
579
+ # and was recovered from and we don't know if the pipette is physically here.
580
+ # This isn't used for path planning, but rather for implicit destination
581
+ # selection like in `pipette.aspirate(location=None)`.
582
+ self._protocol_core.set_last_location(location=location, mount=self.get_mount())
583
+
584
+ def drop_tip(
585
+ self,
586
+ location: Optional[Location],
587
+ well_core: WellCore,
588
+ home_after: Optional[bool],
589
+ alternate_drop_location: Optional[bool] = False,
590
+ ) -> None:
591
+ """Move to and drop a tip into a given well.
592
+
593
+ Args:
594
+ location: The location of the well we're dropping tip into.
595
+ Used to calculate the relative well offset for the drop command.
596
+ well_core: The well we're dropping into
597
+ home_after: Whether to home the pipette after the tip is dropped.
598
+ alternate_drop_location: Whether to randomize the exact location to drop tip
599
+ within the specified well.
600
+ """
601
+ well_name = well_core.get_name()
602
+ labware_id = well_core.labware_id
603
+ scrape_tips = False
604
+
605
+ if location is not None:
606
+ (
607
+ relative_well_location,
608
+ _,
609
+ ) = self._engine_client.state.geometry.get_relative_well_location(
610
+ labware_id=labware_id,
611
+ well_name=well_name,
612
+ absolute_point=location.point,
613
+ location_type=WellLocationFunction.DROP_TIP,
614
+ )
615
+
616
+ well_location = DropTipWellLocation(
617
+ origin=DropTipWellOrigin(relative_well_location.origin.value),
618
+ offset=relative_well_location.offset,
619
+ )
620
+ else:
621
+ well_location = DropTipWellLocation()
622
+
623
+ if self._engine_client.state.labware.is_tiprack(labware_id):
624
+ pipette_movement_conflict.check_safe_for_tip_pickup_and_return(
625
+ engine_state=self._engine_client.state,
626
+ pipette_id=self._pipette_id,
627
+ labware_id=labware_id,
628
+ )
629
+ scrape_tips = self.get_channels() <= 8
630
+ pipette_movement_conflict.check_safe_for_pipette_movement(
631
+ engine_state=self._engine_client.state,
632
+ pipette_id=self._pipette_id,
633
+ labware_id=labware_id,
634
+ well_name=well_name,
635
+ well_location=well_location,
636
+ )
637
+ self._engine_client.execute_command(
638
+ cmd.DropTipParams(
639
+ pipetteId=self._pipette_id,
640
+ labwareId=labware_id,
641
+ wellName=well_name,
642
+ wellLocation=well_location,
643
+ homeAfter=home_after,
644
+ alternateDropLocation=alternate_drop_location,
645
+ scrape_tips=scrape_tips,
646
+ )
647
+ )
648
+
649
+ self._protocol_core.set_last_location(location=location, mount=self.get_mount())
650
+
651
+ def drop_tip_in_disposal_location(
652
+ self,
653
+ disposal_location: Union[TrashBin, WasteChute],
654
+ home_after: Optional[bool],
655
+ alternate_tip_drop: bool = False,
656
+ ) -> None:
657
+ self._move_to_disposal_location(
658
+ disposal_location,
659
+ force_direct=False,
660
+ speed=None,
661
+ alternate_tip_drop=alternate_tip_drop,
662
+ )
663
+ self._drop_tip_in_place(home_after=home_after)
664
+ self._protocol_core.set_last_location(location=None, mount=self.get_mount())
665
+
666
+ def _move_to_disposal_location(
667
+ self,
668
+ disposal_location: Union[TrashBin, WasteChute],
669
+ force_direct: bool,
670
+ speed: Optional[float],
671
+ alternate_tip_drop: bool = False,
672
+ ) -> None:
673
+ # TODO (nd, 2023-11-30): give appropriate offset when finalized
674
+ # https://opentrons.atlassian.net/browse/RSS-391
675
+
676
+ disposal_offset = disposal_location.offset
677
+ offset = AddressableOffsetVector(
678
+ x=disposal_offset.x, y=disposal_offset.y, z=disposal_offset.z
679
+ )
680
+
681
+ if isinstance(disposal_location, TrashBin):
682
+ addressable_area_name = disposal_location.area_name
683
+ self._engine_client.execute_command(
684
+ cmd.MoveToAddressableAreaForDropTipParams(
685
+ pipetteId=self._pipette_id,
686
+ addressableAreaName=addressable_area_name,
687
+ offset=offset,
688
+ forceDirect=force_direct,
689
+ speed=speed,
690
+ minimumZHeight=None,
691
+ alternateDropLocation=alternate_tip_drop,
692
+ ignoreTipConfiguration=True,
693
+ )
694
+ )
695
+
696
+ if isinstance(disposal_location, WasteChute):
697
+ num_channels = self.get_channels()
698
+ addressable_area_name = {
699
+ 1: "1ChannelWasteChute",
700
+ 8: "8ChannelWasteChute",
701
+ 96: "96ChannelWasteChute",
702
+ }[num_channels]
703
+
704
+ self._engine_client.execute_command(
705
+ cmd.MoveToAddressableAreaParams(
706
+ pipetteId=self._pipette_id,
707
+ addressableAreaName=addressable_area_name,
708
+ offset=offset,
709
+ forceDirect=force_direct,
710
+ speed=speed,
711
+ minimumZHeight=None,
712
+ )
713
+ )
714
+
715
+ def _drop_tip_in_place(self, home_after: Optional[bool]) -> None:
716
+ self._engine_client.execute_command(
717
+ cmd.DropTipInPlaceParams(
718
+ pipetteId=self._pipette_id,
719
+ homeAfter=home_after,
720
+ )
721
+ )
722
+
723
+ def home(self) -> None:
724
+ z_axis = self._engine_client.state.pipettes.get_z_axis(self._pipette_id)
725
+ plunger_axis = self._engine_client.state.pipettes.get_plunger_axis(
726
+ self._pipette_id
727
+ )
728
+ self._engine_client.execute_command(cmd.HomeParams(axes=[z_axis, plunger_axis]))
729
+
730
+ def home_plunger(self) -> None:
731
+ plunger_axis = self._engine_client.state.pipettes.get_plunger_axis(
732
+ self._pipette_id
733
+ )
734
+ self._engine_client.execute_command(cmd.HomeParams(axes=[plunger_axis]))
735
+
736
+ def move_to(
737
+ self,
738
+ location: Union[Location, TrashBin, WasteChute],
739
+ well_core: Optional[WellCore],
740
+ force_direct: bool,
741
+ minimum_z_height: Optional[float],
742
+ speed: Optional[float],
743
+ check_for_movement_conflicts: bool = True,
744
+ ) -> None:
745
+ if well_core is not None:
746
+ if isinstance(location, (TrashBin, WasteChute)):
747
+ raise ValueError("Trash Bin and Waste Chute have no Wells.")
748
+ labware_id = well_core.labware_id
749
+ well_name = well_core.get_name()
750
+ (
751
+ well_location,
752
+ _,
753
+ ) = self._engine_client.state.geometry.get_relative_well_location(
754
+ labware_id=labware_id,
755
+ well_name=well_name,
756
+ absolute_point=location.point,
757
+ location_type=WellLocationFunction.LIQUID_HANDLING,
758
+ meniscus_tracking=location._meniscus_tracking,
759
+ )
760
+ assert isinstance(well_location, LiquidHandlingWellLocation)
761
+ # specifying a static volume offset isn't implemented yet
762
+ # well locations at this point will be default have been assigned a
763
+ # volume offset of operationVolume
764
+ if well_location.volumeOffset:
765
+ if (
766
+ well_location.volumeOffset != 0
767
+ and well_location.volumeOffset != "operationVolume"
768
+ ):
769
+ raise ValueError(
770
+ f"volume offset {well_location.volumeOffset} not supported with move_to"
771
+ )
772
+ if check_for_movement_conflicts:
773
+ pipette_movement_conflict.check_safe_for_pipette_movement(
774
+ engine_state=self._engine_client.state,
775
+ pipette_id=self._pipette_id,
776
+ labware_id=labware_id,
777
+ well_name=well_name,
778
+ well_location=well_location,
779
+ )
780
+ self._engine_client.execute_command(
781
+ cmd.MoveToWellParams(
782
+ pipetteId=self._pipette_id,
783
+ labwareId=labware_id,
784
+ wellName=well_name,
785
+ wellLocation=well_location,
786
+ minimumZHeight=minimum_z_height,
787
+ forceDirect=force_direct,
788
+ speed=speed,
789
+ )
790
+ )
791
+ else:
792
+ if isinstance(location, (TrashBin, WasteChute)):
793
+ self._move_to_disposal_location(
794
+ disposal_location=location, force_direct=force_direct, speed=speed
795
+ )
796
+ else:
797
+ self._engine_client.execute_command(
798
+ cmd.MoveToCoordinatesParams(
799
+ pipetteId=self._pipette_id,
800
+ coordinates=DeckPoint(
801
+ x=location.point.x, y=location.point.y, z=location.point.z
802
+ ),
803
+ minimumZHeight=minimum_z_height,
804
+ forceDirect=force_direct,
805
+ speed=speed,
806
+ )
807
+ )
808
+
809
+ self._protocol_core.set_last_location(location=location, mount=self.get_mount())
810
+
811
+ def resin_tip_seal(
812
+ self, location: Location, well_core: WellCore, in_place: Optional[bool] = False
813
+ ) -> None:
814
+ labware_id = well_core.labware_id
815
+ well_name = well_core.get_name()
816
+ (
817
+ well_location,
818
+ _,
819
+ ) = self._engine_client.state.geometry.get_relative_well_location(
820
+ labware_id=labware_id,
821
+ well_name=well_name,
822
+ absolute_point=location.point,
823
+ location_type=WellLocationFunction.PICK_UP_TIP,
824
+ )
825
+ assert isinstance(well_location, PickUpTipWellLocation)
826
+ self._engine_client.execute_command(
827
+ cmd.SealPipetteToTipParams(
828
+ pipetteId=self._pipette_id,
829
+ labwareId=labware_id,
830
+ wellName=well_name,
831
+ wellLocation=well_location,
832
+ )
833
+ )
834
+
835
+ def resin_tip_unseal(self, location: Location | None, well_core: WellCore) -> None:
836
+ well_name = well_core.get_name()
837
+ labware_id = well_core.labware_id
838
+
839
+ if location is not None:
840
+ (
841
+ relative_well_location,
842
+ _,
843
+ ) = self._engine_client.state.geometry.get_relative_well_location(
844
+ labware_id=labware_id,
845
+ well_name=well_name,
846
+ absolute_point=location.point,
847
+ location_type=WellLocationFunction.BASE,
848
+ )
849
+
850
+ well_location = DropTipWellLocation(
851
+ origin=DropTipWellOrigin(relative_well_location.origin.value),
852
+ offset=relative_well_location.offset,
853
+ )
854
+ else:
855
+ well_location = DropTipWellLocation()
856
+
857
+ pipette_movement_conflict.check_safe_for_pipette_movement(
858
+ engine_state=self._engine_client.state,
859
+ pipette_id=self._pipette_id,
860
+ labware_id=labware_id,
861
+ well_name=well_name,
862
+ well_location=well_location,
863
+ )
864
+ self._engine_client.execute_command(
865
+ cmd.UnsealPipetteFromTipParams(
866
+ pipetteId=self._pipette_id,
867
+ labwareId=labware_id,
868
+ wellName=well_name,
869
+ wellLocation=well_location,
870
+ )
871
+ )
872
+
873
+ self._protocol_core.set_last_location(location=location, mount=self.get_mount())
874
+
875
+ def resin_tip_dispense(
876
+ self,
877
+ location: Location,
878
+ well_core: WellCore,
879
+ volume: Optional[float] = None,
880
+ flow_rate: Optional[float] = None,
881
+ ) -> None:
882
+ """
883
+ Args:
884
+ volume: The volume of liquid to dispense, in microliters. Defaults to 400uL.
885
+ location: The exact location to dispense to.
886
+ well_core: The well to dispense to, if applicable.
887
+ flow_rate: The flow rate in µL/s to dispense at. Defaults to 10.0uL/S.
888
+ """
889
+ if isinstance(location, (TrashBin, WasteChute)):
890
+ raise ValueError("Trash Bin and Waste Chute have no Wells.")
891
+ well_name = well_core.get_name()
892
+ labware_id = well_core.labware_id
893
+ if volume is None:
894
+ volume = _RESIN_TIP_DEFAULT_VOLUME
895
+ if flow_rate is None:
896
+ flow_rate = _RESIN_TIP_DEFAULT_FLOW_RATE
897
+
898
+ (
899
+ well_location,
900
+ dynamic_tracking,
901
+ ) = self._engine_client.state.geometry.get_relative_well_location(
902
+ labware_id=labware_id,
903
+ well_name=well_name,
904
+ absolute_point=location.point,
905
+ location_type=WellLocationFunction.LIQUID_HANDLING,
906
+ )
907
+ pipette_movement_conflict.check_safe_for_pipette_movement(
908
+ engine_state=self._engine_client.state,
909
+ pipette_id=self._pipette_id,
910
+ labware_id=labware_id,
911
+ well_name=well_name,
912
+ well_location=well_location,
913
+ )
914
+ assert isinstance(well_location, LiquidHandlingWellLocation)
915
+ self._engine_client.execute_command(
916
+ cmd.PressureDispenseParams(
917
+ pipetteId=self._pipette_id,
918
+ labwareId=labware_id,
919
+ wellName=well_name,
920
+ wellLocation=well_location,
921
+ volume=volume,
922
+ flowRate=flow_rate,
923
+ )
924
+ )
925
+
926
+ def get_mount(self) -> Mount:
927
+ """Get the mount the pipette is attached to."""
928
+ return self._engine_client.state.pipettes.get(
929
+ self._pipette_id
930
+ ).mount.to_hw_mount()
931
+
932
+ def get_pipette_name(self) -> str:
933
+ """Get the pipette's name as a string.
934
+
935
+ Will match the load name of the actually loaded pipette,
936
+ which may differ from the requested load name.
937
+
938
+ From API v2.15 to v2.22, this property returned an internal, engine-specific,
939
+ name for Flex pipettes (eg, "p50_multi_flex" instead of "flex_8channel_50").
940
+
941
+ From API v2.23 onwards, this behavior is fixed so that this property returns
942
+ the API-specific names of Flex pipettes.
943
+ """
944
+ # TODO (tz, 11-23-22): revert this change when merging
945
+ # https://opentrons.atlassian.net/browse/RLIQ-251
946
+ pipette = self._engine_client.state.pipettes.get(self._pipette_id)
947
+ if self._protocol_core.api_version < _FLEX_PIPETTE_NAMES_FIXED_IN:
948
+ return pipette.pipetteName.value
949
+ else:
950
+ name = next(
951
+ (
952
+ pip_api_name
953
+ for pip_api_name, pip_name in PIPETTE_API_NAMES_MAP.items()
954
+ if pip_name == pipette.pipetteName
955
+ ),
956
+ None,
957
+ )
958
+ assert name, "Pipette name not found."
959
+ return name
960
+
961
+ def get_model(self) -> str:
962
+ return self._engine_client.state.pipettes.get_model_name(self._pipette_id)
963
+
964
+ def get_display_name(self) -> str:
965
+ return self._engine_client.state.pipettes.get_display_name(self._pipette_id)
966
+
967
+ def get_min_volume(self) -> float:
968
+ return self._engine_client.state.pipettes.get_minimum_volume(self._pipette_id)
969
+
970
+ def get_max_volume(self) -> float:
971
+ return self._engine_client.state.pipettes.get_maximum_volume(self._pipette_id)
972
+
973
+ def get_working_volume(self) -> float:
974
+ return self._engine_client.state.pipettes.get_working_volume(self._pipette_id)
975
+
976
+ def get_current_volume(self) -> float:
977
+ try:
978
+ current_volume = self._engine_client.state.pipettes.get_aspirated_volume(
979
+ self._pipette_id
980
+ )
981
+ except TipNotAttachedError:
982
+ current_volume = None
983
+
984
+ return current_volume or 0
985
+
986
+ def get_has_clean_tip(self) -> bool:
987
+ try:
988
+ clean_tip = self._engine_client.state.pipettes.get_has_clean_tip(
989
+ self._pipette_id
990
+ )
991
+ except TipNotAttachedError:
992
+ clean_tip = False
993
+
994
+ return clean_tip
995
+
996
+ def get_available_volume(self) -> float:
997
+ try:
998
+ available_volume = self._engine_client.state.pipettes.get_available_volume(
999
+ self._pipette_id
1000
+ )
1001
+ except TipNotAttachedError:
1002
+ available_volume = None
1003
+
1004
+ return available_volume or 0
1005
+
1006
+ def get_hardware_state(self) -> PipetteDict:
1007
+ """Get the current state of the pipette hardware as a dictionary."""
1008
+ return self._sync_hardware_api.get_attached_instrument(self.get_mount()) # type: ignore[no-any-return]
1009
+
1010
+ def get_channels(self) -> int:
1011
+ return self._engine_client.state.pipettes.get_channels(self._pipette_id)
1012
+
1013
+ def get_active_channels(self) -> int:
1014
+ return self._engine_client.state.pipettes.get_active_channels(self._pipette_id)
1015
+
1016
+ def get_nozzle_map(self) -> NozzleMapInterface:
1017
+ return self._engine_client.state.pipettes.get_nozzle_configuration(
1018
+ self._pipette_id
1019
+ )
1020
+
1021
+ def has_tip(self) -> bool:
1022
+ return (
1023
+ self._engine_client.state.pipettes.get_attached_tip(self._pipette_id)
1024
+ is not None
1025
+ )
1026
+
1027
+ def get_return_height(self) -> float:
1028
+ return self._engine_client.state.pipettes.get_return_tip_scale(self._pipette_id)
1029
+
1030
+ def get_flow_rate(self) -> FlowRates:
1031
+ return self._flow_rates
1032
+
1033
+ def get_aspirate_flow_rate(self, rate: float = 1.0) -> float:
1034
+ return self._aspirate_flow_rate * rate
1035
+
1036
+ def get_dispense_flow_rate(self, rate: float = 1.0) -> float:
1037
+ return self._dispense_flow_rate * rate
1038
+
1039
+ def get_blow_out_flow_rate(self, rate: float = 1.0) -> float:
1040
+ return self._blow_out_flow_rate * rate
1041
+
1042
+ def get_nozzle_configuration(self) -> NozzleConfigurationType:
1043
+ return self._engine_client.state.pipettes.get_nozzle_layout_type(
1044
+ self._pipette_id
1045
+ )
1046
+
1047
+ def get_liquid_presence_detection(self) -> bool:
1048
+ return self._liquid_presence_detection
1049
+
1050
+ def get_tip_origin(
1051
+ self,
1052
+ ) -> Optional[Tuple[LabwareCore, WellCore]]:
1053
+ last_tip_pickup_info = (
1054
+ self._engine_client.state.pipettes.get_tip_rack_well_picked_up_from(
1055
+ self._pipette_id
1056
+ )
1057
+ )
1058
+ if last_tip_pickup_info is None:
1059
+ return None
1060
+ else:
1061
+ tip_rack_labware_core = self._protocol_core._labware_cores_by_id[
1062
+ last_tip_pickup_info.labware_id
1063
+ ]
1064
+ tip_well_core = tip_rack_labware_core.get_well_core(
1065
+ last_tip_pickup_info.well_name
1066
+ )
1067
+ return tip_rack_labware_core, tip_well_core
1068
+
1069
+ def is_tip_tracking_available(self) -> bool:
1070
+ if self.get_nozzle_configuration() == NozzleConfigurationType.FULL:
1071
+ return True
1072
+ else:
1073
+ if self.get_channels() == 96:
1074
+ return True
1075
+ if self.get_channels() == 8:
1076
+ return True
1077
+ return False
1078
+
1079
+ def set_flow_rate(
1080
+ self,
1081
+ aspirate: Optional[float] = None,
1082
+ dispense: Optional[float] = None,
1083
+ blow_out: Optional[float] = None,
1084
+ ) -> None:
1085
+ if aspirate is not None:
1086
+ assert aspirate > 0
1087
+ self._aspirate_flow_rate = aspirate
1088
+ if dispense is not None:
1089
+ assert dispense > 0
1090
+ self._dispense_flow_rate = dispense
1091
+ if blow_out is not None:
1092
+ assert blow_out > 0
1093
+ self._blow_out_flow_rate = blow_out
1094
+
1095
+ def set_liquid_presence_detection(self, enable: bool) -> None:
1096
+ self._liquid_presence_detection = enable
1097
+
1098
+ def configure_for_volume(self, volume: float) -> None:
1099
+ self._engine_client.execute_command(
1100
+ cmd.ConfigureForVolumeParams(
1101
+ pipetteId=self._pipette_id,
1102
+ volume=volume,
1103
+ tipOverlapNotAfterVersion=overlap_versions.overlap_for_api_version(
1104
+ self._protocol_core.api_version
1105
+ ),
1106
+ )
1107
+ )
1108
+
1109
+ def prepare_to_aspirate(self) -> None:
1110
+ self._engine_client.execute_command(
1111
+ cmd.PrepareToAspirateParams(pipetteId=self._pipette_id)
1112
+ )
1113
+
1114
+ def configure_nozzle_layout(
1115
+ self,
1116
+ style: NozzleLayout,
1117
+ primary_nozzle: Optional[str],
1118
+ front_right_nozzle: Optional[str],
1119
+ back_left_nozzle: Optional[str],
1120
+ ) -> None:
1121
+ if style == NozzleLayout.COLUMN:
1122
+ configuration_model: NozzleLayoutConfigurationType = (
1123
+ ColumnNozzleLayoutConfiguration(
1124
+ primaryNozzle=cast(PRIMARY_NOZZLE_LITERAL, primary_nozzle)
1125
+ )
1126
+ )
1127
+ elif style == NozzleLayout.ROW:
1128
+ configuration_model = RowNozzleLayoutConfiguration(
1129
+ primaryNozzle=cast(PRIMARY_NOZZLE_LITERAL, primary_nozzle)
1130
+ )
1131
+ elif style == NozzleLayout.QUADRANT or style == NozzleLayout.PARTIAL_COLUMN:
1132
+ assert (
1133
+ # We make sure to set these nozzles in the calling function
1134
+ # if using QUADRANT or PARTIAL_COLUMN. Asserting only for type verification here.
1135
+ front_right_nozzle is not None
1136
+ and back_left_nozzle is not None
1137
+ ), f"Both front right and back left nozzles are required for {style} configuration."
1138
+ configuration_model = QuadrantNozzleLayoutConfiguration(
1139
+ primaryNozzle=cast(PRIMARY_NOZZLE_LITERAL, primary_nozzle),
1140
+ frontRightNozzle=front_right_nozzle,
1141
+ backLeftNozzle=back_left_nozzle,
1142
+ )
1143
+ elif style == NozzleLayout.SINGLE:
1144
+ configuration_model = SingleNozzleLayoutConfiguration(
1145
+ primaryNozzle=cast(PRIMARY_NOZZLE_LITERAL, primary_nozzle)
1146
+ )
1147
+ else:
1148
+ configuration_model = AllNozzleLayoutConfiguration()
1149
+ self._engine_client.execute_command(
1150
+ cmd.ConfigureNozzleLayoutParams(
1151
+ pipetteId=self._pipette_id, configurationParams=configuration_model
1152
+ )
1153
+ )
1154
+
1155
+ def load_liquid_class(
1156
+ self,
1157
+ name: str,
1158
+ transfer_properties: TransferProperties,
1159
+ tiprack_uri: str,
1160
+ ) -> str:
1161
+ """Load a liquid class into the engine and return its ID.
1162
+
1163
+ Args:
1164
+ name: Name of the liquid class
1165
+ transfer_properties: Liquid class properties for a specific pipette & tiprack combination
1166
+ tiprack_uri: URI of the tiprack whose transfer properties we will be using.
1167
+
1168
+ Returns:
1169
+ Liquid class record's ID, as generated by the protocol engine.
1170
+ """
1171
+ liquid_class_record = LiquidClassRecord(
1172
+ liquidClassName=name,
1173
+ pipetteModel=self.get_pipette_name(),
1174
+ tiprack=tiprack_uri,
1175
+ aspirate=transfer_properties.aspirate.as_shared_data_model(),
1176
+ singleDispense=transfer_properties.dispense.as_shared_data_model(),
1177
+ multiDispense=(
1178
+ transfer_properties.multi_dispense.as_shared_data_model()
1179
+ if transfer_properties.multi_dispense
1180
+ else None
1181
+ ),
1182
+ )
1183
+ result = self._engine_client.execute_command_without_recovery(
1184
+ cmd.LoadLiquidClassParams(
1185
+ liquidClassRecord=liquid_class_record,
1186
+ )
1187
+ )
1188
+ return result.liquidClassId
1189
+
1190
+ def get_next_tip(
1191
+ self, tip_racks: List[LabwareCore], starting_well: Optional[WellCore]
1192
+ ) -> Optional[NextTipInfo]:
1193
+ """Get the next tip to pick up."""
1194
+ if starting_well is not None:
1195
+ # Drop tip racks until the one with the starting tip is reached (if any)
1196
+ valid_tip_racks = list(
1197
+ dropwhile(
1198
+ lambda tr: starting_well.labware_id != tr.labware_id, tip_racks
1199
+ )
1200
+ )
1201
+ else:
1202
+ valid_tip_racks = tip_racks
1203
+
1204
+ result = self._engine_client.execute_command_without_recovery(
1205
+ cmd.GetNextTipParams(
1206
+ pipetteId=self._pipette_id,
1207
+ labwareIds=[tip_rack.labware_id for tip_rack in valid_tip_racks],
1208
+ startingTipWell=(
1209
+ starting_well.get_name() if starting_well is not None else None
1210
+ ),
1211
+ )
1212
+ )
1213
+ next_tip_info = result.nextTipInfo
1214
+ if isinstance(next_tip_info, NoTipAvailable):
1215
+ if next_tip_info.noTipReason == NoTipReason.STARTING_TIP_WITH_PARTIAL:
1216
+ raise CommandPreconditionViolated(
1217
+ "Automatic tip tracking is not available when using a partial pipette"
1218
+ " nozzle configuration and InstrumentContext.starting_tip."
1219
+ " Switch to a full configuration or set starting_tip to None."
1220
+ )
1221
+ return None
1222
+ else:
1223
+ return next_tip_info
1224
+
1225
+ def transfer_with_liquid_class( # noqa: C901
1226
+ self,
1227
+ liquid_class: LiquidClass,
1228
+ volume: float,
1229
+ source: List[Tuple[Location, WellCore]],
1230
+ dest: Union[List[Tuple[Location, WellCore]], TrashBin, WasteChute],
1231
+ new_tip: TransferTipPolicyV2,
1232
+ tip_racks: List[Tuple[Location, LabwareCore]],
1233
+ starting_tip: Optional[WellCore],
1234
+ trash_location: Union[Location, TrashBin, WasteChute],
1235
+ return_tip: bool,
1236
+ keep_last_tip: bool,
1237
+ ) -> None:
1238
+ """Execute transfer using liquid class properties.
1239
+
1240
+ Args:
1241
+ liquid_class: The liquid class to use for transfer properties.
1242
+ volume: Volume to transfer per well.
1243
+ source: List of source wells, with each well represented as a tuple of
1244
+ types.Location and WellCore.
1245
+ types.Location is only necessary for saving the last accessed location.
1246
+ dest: List of destination wells, with each well represented as a tuple of
1247
+ types.Location and WellCore.
1248
+ types.Location is only necessary for saving the last accessed location.
1249
+ new_tip: Whether the transfer should use a new tip 'once', 'never', 'always',
1250
+ or 'per source'.
1251
+ tip_racks: List of tipracks that the transfer will pick up tips from, represented
1252
+ as tuples of types.Location and WellCore.
1253
+ starting_tip: The user-chosen starting tip to use when deciding what tip to pick
1254
+ up, if the user has set it.
1255
+ trash_location: The chosen trash container to drop tips in and dispose liquid in.
1256
+ return_tip: If `True`, return tips to the tip rack location they were picked up from,
1257
+ otherwise drop in `trash_location`
1258
+ keep_last_tip: When set to `True`, do not drop the final tip used in the transfer.
1259
+ """
1260
+ if not tip_racks:
1261
+ raise RuntimeError(
1262
+ "No tipracks found for pipette in order to perform transfer"
1263
+ )
1264
+ tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
1265
+ transfer_props = self._get_transfer_properties_for_tip_rack(
1266
+ liquid_class, tiprack_uri_for_transfer_props
1267
+ )
1268
+
1269
+ # TODO: use the ID returned by load_liquid_class in command annotations
1270
+ self.load_liquid_class(
1271
+ name=liquid_class.name,
1272
+ transfer_properties=transfer_props,
1273
+ tiprack_uri=tiprack_uri_for_transfer_props,
1274
+ )
1275
+
1276
+ target_destinations: Sequence[
1277
+ Union[Tuple[Location, WellCore], TrashBin, WasteChute]
1278
+ ]
1279
+ if isinstance(dest, (TrashBin, WasteChute)):
1280
+ target_destinations = [dest] * len(source)
1281
+ else:
1282
+ target_destinations = dest
1283
+
1284
+ working_volume = self.get_working_volume_for_tip_rack(tip_racks[0][1])
1285
+
1286
+ source_dest_per_volume_step = (
1287
+ tx_commons.get_sources_and_destinations_for_liquid_classes(
1288
+ volumes=[volume for _ in range(len(source))],
1289
+ max_volume=working_volume,
1290
+ targets=zip(source, target_destinations),
1291
+ transfer_properties=transfer_props,
1292
+ )
1293
+ )
1294
+
1295
+ if new_tip == TransferTipPolicyV2.ONCE:
1296
+ self._pick_up_tip_for_liquid_class(
1297
+ tip_racks, starting_tip, tiprack_uri_for_transfer_props
1298
+ )
1299
+
1300
+ prev_src: Optional[Tuple[Location, WellCore]] = None
1301
+ prev_dest: Optional[
1302
+ Union[Tuple[Location, WellCore], TrashBin, WasteChute]
1303
+ ] = None
1304
+ post_disp_tip_contents = [
1305
+ tx_comps_executor.LiquidAndAirGapPair(
1306
+ liquid=0,
1307
+ air_gap=0,
1308
+ )
1309
+ ]
1310
+ next_step_volume, next_src_dest_combo = next(source_dest_per_volume_step)
1311
+ is_last_step = False
1312
+ while not is_last_step:
1313
+ step_volume = next_step_volume
1314
+ src_dest_combo = next_src_dest_combo
1315
+ step_source, step_destination = src_dest_combo
1316
+ try:
1317
+ next_step_volume, next_src_dest_combo = next(
1318
+ source_dest_per_volume_step
1319
+ )
1320
+ except StopIteration:
1321
+ is_last_step = True
1322
+
1323
+ if (
1324
+ new_tip == TransferTipPolicyV2.ALWAYS
1325
+ or (
1326
+ new_tip == TransferTipPolicyV2.PER_SOURCE
1327
+ and step_source != prev_src
1328
+ )
1329
+ or (
1330
+ new_tip == TransferTipPolicyV2.PER_DESTINATION
1331
+ and step_destination != prev_dest
1332
+ )
1333
+ ):
1334
+ if prev_src is not None and prev_dest is not None:
1335
+ self._drop_tip_for_liquid_class(trash_location, return_tip)
1336
+ self._pick_up_tip_for_liquid_class(
1337
+ tip_racks, starting_tip, tiprack_uri_for_transfer_props
1338
+ )
1339
+ post_disp_tip_contents = [
1340
+ tx_comps_executor.LiquidAndAirGapPair(
1341
+ liquid=0,
1342
+ air_gap=0,
1343
+ )
1344
+ ]
1345
+
1346
+ post_asp_tip_contents = self.aspirate_liquid_class(
1347
+ volume=step_volume,
1348
+ source=step_source,
1349
+ transfer_properties=transfer_props,
1350
+ transfer_type=tx_comps_executor.TransferType.ONE_TO_ONE,
1351
+ tip_contents=post_disp_tip_contents,
1352
+ volume_for_pipette_mode_configuration=step_volume,
1353
+ )
1354
+ post_disp_tip_contents = self.dispense_liquid_class(
1355
+ volume=step_volume,
1356
+ dest=step_destination,
1357
+ source=step_source,
1358
+ transfer_properties=transfer_props,
1359
+ transfer_type=tx_comps_executor.TransferType.ONE_TO_ONE,
1360
+ tip_contents=post_asp_tip_contents,
1361
+ add_final_air_gap=(False if is_last_step and keep_last_tip else True),
1362
+ trash_location=trash_location,
1363
+ )
1364
+ prev_src = step_source
1365
+ prev_dest = step_destination
1366
+
1367
+ if not keep_last_tip:
1368
+ self._drop_tip_for_liquid_class(trash_location, return_tip)
1369
+
1370
+ def distribute_with_liquid_class( # noqa: C901
1371
+ self,
1372
+ liquid_class: LiquidClass,
1373
+ volume: float,
1374
+ source: Tuple[Location, WellCore],
1375
+ dest: List[Tuple[Location, WellCore]],
1376
+ new_tip: Literal[
1377
+ TransferTipPolicyV2.NEVER,
1378
+ TransferTipPolicyV2.ONCE,
1379
+ TransferTipPolicyV2.ALWAYS,
1380
+ ],
1381
+ tip_racks: List[Tuple[Location, LabwareCore]],
1382
+ starting_tip: Optional[WellCore],
1383
+ trash_location: Union[Location, TrashBin, WasteChute],
1384
+ return_tip: bool,
1385
+ keep_last_tip: bool,
1386
+ ) -> None:
1387
+ """Execute a distribution using liquid class properties.
1388
+
1389
+ Args:
1390
+ liquid_class: The liquid class to use for transfer properties.
1391
+ volume: The amount of liquid in uL, to dispense into each destination well.
1392
+ source: Source well represented as a tuple of types.Location and WellCore.
1393
+ types.Location is only necessary for saving the last accessed location.
1394
+ dest: List of destination wells, with each well represented as a tuple of
1395
+ types.Location and WellCore.
1396
+ types.Location is only necessary for saving the last accessed location.
1397
+ new_tip: Whether the transfer should use a new tip 'once', 'always' or 'never'.
1398
+ 'never': the transfer will never pick up a new tip
1399
+ 'once': the transfer will pick up a new tip once at the start of transfer
1400
+ 'always': the transfer will pick up a new tip before every aspirate
1401
+ tip_racks: List of tipracks that the transfer will pick up tips from, represented
1402
+ as tuples of types.Location and WellCore.
1403
+ starting_tip: The user-chosen starting tip to use when deciding what tip to pick
1404
+ up, if the user has set it.
1405
+ trash_location: The chosen trash container to drop tips in and dispose liquid in.
1406
+ return_tip: If `True`, return tips to the tip rack location they were picked up from,
1407
+ otherwise drop in `trash_location`
1408
+ keep_last_tip: When set to `True`, do not drop the final tip used in the distribute.
1409
+
1410
+ This method distributes the liquid in the source well into multiple destinations.
1411
+ It can accomplish this by either doing a multi-dispense (aspirate once and then
1412
+ dispense multiple times consecutively) or by doing multiple single-dispenses
1413
+ (going back to aspirate after each dispense). Whether it does a multi-dispense or
1414
+ multiple single dispenses is determined by whether multi-dispense properties
1415
+ are available in the liquid class and whether the tip in use can hold multiple
1416
+ volumes to be dispensed without having to refill.
1417
+ """
1418
+ if not tip_racks:
1419
+ raise RuntimeError(
1420
+ "No tipracks found for pipette in order to perform transfer"
1421
+ )
1422
+ assert new_tip in [
1423
+ TransferTipPolicyV2.NEVER,
1424
+ TransferTipPolicyV2.ONCE,
1425
+ TransferTipPolicyV2.ALWAYS,
1426
+ ]
1427
+
1428
+ tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
1429
+ transfer_props = self._get_transfer_properties_for_tip_rack(
1430
+ liquid_class, tiprack_uri_for_transfer_props
1431
+ )
1432
+
1433
+ # If the volume to dispense into a well is less than threshold for low volume mode,
1434
+ # then set the max working volume to the max volume of low volume mode.
1435
+ # NOTE: this logic will need to be updated once we support list of volumes
1436
+ # TODO (spp): refactor this to use the volume thresholds from shared data
1437
+ has_low_volume_mode = self.get_pipette_name() in [
1438
+ "flex_1channel_50",
1439
+ "flex_8channel_50",
1440
+ ]
1441
+ working_volume = self.get_working_volume_for_tip_rack(tip_racks[0][1])
1442
+ if has_low_volume_mode and volume < 5:
1443
+ working_volume = 30
1444
+ # If there are no multi-dispense properties or if the volume to distribute
1445
+ # per destination well is so large that the tip cannot hold enough liquid
1446
+ # to consecutively distribute to at least two wells, then we resort to using
1447
+ # a regular, one-to-one transfer to carry out the distribution.
1448
+ min_asp_vol_for_multi_dispense = 2 * volume
1449
+ if (
1450
+ transfer_props.multi_dispense is None
1451
+ or not self._tip_can_hold_volume_for_multi_dispensing(
1452
+ transfer_volume=min_asp_vol_for_multi_dispense,
1453
+ multi_dispense_properties=transfer_props.multi_dispense,
1454
+ tip_working_volume=working_volume,
1455
+ )
1456
+ ):
1457
+ return self.transfer_with_liquid_class(
1458
+ liquid_class=liquid_class,
1459
+ volume=volume,
1460
+ source=[source for _ in range(len(dest))],
1461
+ dest=dest,
1462
+ new_tip=new_tip,
1463
+ tip_racks=tip_racks,
1464
+ starting_tip=starting_tip,
1465
+ trash_location=trash_location,
1466
+ return_tip=return_tip,
1467
+ keep_last_tip=keep_last_tip,
1468
+ )
1469
+
1470
+ # TODO: use the ID returned by load_liquid_class in command annotations
1471
+ self.load_liquid_class(
1472
+ name=liquid_class.name,
1473
+ transfer_properties=transfer_props,
1474
+ tiprack_uri=tiprack_uri_for_transfer_props,
1475
+ )
1476
+
1477
+ # This will return a generator that provides pairs of destination well and
1478
+ # the volume to dispense into it
1479
+ dest_per_volume_step = (
1480
+ tx_commons.get_sources_and_destinations_for_liquid_classes(
1481
+ volumes=[volume for _ in range(len(dest))],
1482
+ max_volume=working_volume,
1483
+ targets=dest,
1484
+ transfer_properties=transfer_props,
1485
+ is_multi_dispense=True,
1486
+ )
1487
+ )
1488
+
1489
+ if new_tip != TransferTipPolicyV2.NEVER:
1490
+ self._pick_up_tip_for_liquid_class(
1491
+ tip_racks, starting_tip, tiprack_uri_for_transfer_props
1492
+ )
1493
+
1494
+ tip_contents = [
1495
+ tx_comps_executor.LiquidAndAirGapPair(
1496
+ liquid=0,
1497
+ air_gap=0,
1498
+ )
1499
+ ]
1500
+ next_step_volume, next_dest = next(dest_per_volume_step)
1501
+ is_last_step = False
1502
+ is_first_step = True
1503
+
1504
+ # This loop will run until the last step has been executed
1505
+ while not is_last_step:
1506
+ total_aspirate_volume = 0.0
1507
+ vol_dest_combo = []
1508
+
1509
+ # This loop looks at the next volumes to dispense and calculates how many
1510
+ # dispense volumes plus their conditioning & disposal volumes can fit into
1511
+ # the tip. It then collects these volumes and their destinations in a list.
1512
+ while not is_last_step and self._tip_can_hold_volume_for_multi_dispensing(
1513
+ transfer_volume=total_aspirate_volume + next_step_volume,
1514
+ multi_dispense_properties=transfer_props.multi_dispense,
1515
+ tip_working_volume=working_volume,
1516
+ ):
1517
+ total_aspirate_volume += next_step_volume
1518
+ vol_dest_combo.append((next_step_volume, next_dest))
1519
+ try:
1520
+ next_step_volume, next_dest = next(dest_per_volume_step)
1521
+ except StopIteration:
1522
+ is_last_step = True
1523
+
1524
+ conditioning_vol = (
1525
+ transfer_props.multi_dispense.conditioning_by_volume.get_for_volume(
1526
+ total_aspirate_volume
1527
+ )
1528
+ )
1529
+ disposal_vol = (
1530
+ transfer_props.multi_dispense.disposal_by_volume.get_for_volume(
1531
+ total_aspirate_volume
1532
+ )
1533
+ )
1534
+
1535
+ use_single_dispense = False
1536
+ if total_aspirate_volume == volume and len(vol_dest_combo) == 1:
1537
+ # We are only doing a single transfer. Either because this is the last
1538
+ # remaining volume to dispense or, once this function accepts a list of
1539
+ # volumes, the next pair of volumes is too large to be multi-dispensed.
1540
+ # So we won't use conditioning volume or disposal volume
1541
+ conditioning_vol = 0
1542
+ disposal_vol = 0
1543
+ use_single_dispense = True
1544
+
1545
+ if (
1546
+ not use_single_dispense
1547
+ and disposal_vol > 0
1548
+ and not transfer_props.multi_dispense.retract.blowout.enabled
1549
+ ):
1550
+ raise RuntimeError(
1551
+ "Distribute uses a disposal volume but location for disposing of"
1552
+ " the disposal volume cannot be found when blowout is disabled."
1553
+ " Specify a blowout location and enable blowout when using a disposal volume."
1554
+ )
1555
+
1556
+ if not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
1557
+ self._drop_tip_for_liquid_class(trash_location, return_tip)
1558
+ self._pick_up_tip_for_liquid_class(
1559
+ tip_racks, starting_tip, tiprack_uri_for_transfer_props
1560
+ )
1561
+ tip_contents = [
1562
+ tx_comps_executor.LiquidAndAirGapPair(
1563
+ liquid=0,
1564
+ air_gap=0,
1565
+ )
1566
+ ]
1567
+ # Aspirate the total volume determined by the loop above
1568
+ tip_contents = self.aspirate_liquid_class(
1569
+ volume=total_aspirate_volume + conditioning_vol + disposal_vol,
1570
+ source=source,
1571
+ transfer_properties=transfer_props,
1572
+ transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
1573
+ tip_contents=tip_contents,
1574
+ # We configure the mode based on the last dispense volume and disposal volume
1575
+ # since the mode is only used to determine the dispense push out volume
1576
+ # and we can do a push out only at the last dispense, that too if there is no disposal volume.
1577
+ volume_for_pipette_mode_configuration=vol_dest_combo[-1][0],
1578
+ conditioning_volume=conditioning_vol,
1579
+ )
1580
+
1581
+ # If the tip has volumes corresponding to multiple destinations, then
1582
+ # multi-dispense in those destinations.
1583
+ # If the tip has a volume corresponding to a single destination, then
1584
+ # do a single-dispense into that destination.
1585
+ for idx, (dispense_vol, dispense_dest) in enumerate(vol_dest_combo):
1586
+ if use_single_dispense:
1587
+ tip_contents = self.dispense_liquid_class(
1588
+ volume=dispense_vol,
1589
+ dest=dispense_dest,
1590
+ source=source,
1591
+ transfer_properties=transfer_props,
1592
+ transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
1593
+ tip_contents=tip_contents,
1594
+ add_final_air_gap=(
1595
+ False if is_last_step and keep_last_tip else True
1596
+ ),
1597
+ trash_location=trash_location,
1598
+ )
1599
+ else:
1600
+ tip_contents = self.dispense_liquid_class_during_multi_dispense(
1601
+ volume=dispense_vol,
1602
+ dest=dispense_dest,
1603
+ source=source,
1604
+ transfer_properties=transfer_props,
1605
+ transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
1606
+ tip_contents=tip_contents,
1607
+ add_final_air_gap=(
1608
+ False if is_last_step and keep_last_tip else True
1609
+ ),
1610
+ trash_location=trash_location,
1611
+ conditioning_volume=conditioning_vol,
1612
+ disposal_volume=disposal_vol,
1613
+ is_last_dispense_in_tip=(idx == len(vol_dest_combo) - 1),
1614
+ )
1615
+ is_first_step = False
1616
+
1617
+ if not keep_last_tip:
1618
+ self._drop_tip_for_liquid_class(trash_location, return_tip)
1619
+
1620
+ def _tip_can_hold_volume_for_multi_dispensing(
1621
+ self,
1622
+ transfer_volume: float,
1623
+ multi_dispense_properties: MultiDispenseProperties,
1624
+ tip_working_volume: float,
1625
+ ) -> bool:
1626
+ """
1627
+ Whether the tip can hold the volume plus the conditioning and disposal volumes
1628
+ required for multi-dispensing.
1629
+ """
1630
+ return (
1631
+ transfer_volume
1632
+ + multi_dispense_properties.conditioning_by_volume.get_for_volume(
1633
+ transfer_volume
1634
+ )
1635
+ + multi_dispense_properties.disposal_by_volume.get_for_volume(
1636
+ transfer_volume
1637
+ )
1638
+ <= tip_working_volume
1639
+ )
1640
+
1641
+ def consolidate_with_liquid_class( # noqa: C901
1642
+ self,
1643
+ liquid_class: LiquidClass,
1644
+ volume: float,
1645
+ source: List[Tuple[Location, WellCore]],
1646
+ dest: Union[Tuple[Location, WellCore], TrashBin, WasteChute],
1647
+ new_tip: Literal[
1648
+ TransferTipPolicyV2.NEVER,
1649
+ TransferTipPolicyV2.ONCE,
1650
+ TransferTipPolicyV2.ALWAYS,
1651
+ ],
1652
+ tip_racks: List[Tuple[Location, LabwareCore]],
1653
+ starting_tip: Optional[WellCore],
1654
+ trash_location: Union[Location, TrashBin, WasteChute],
1655
+ return_tip: bool,
1656
+ keep_last_tip: bool,
1657
+ ) -> None:
1658
+ """Execute consolidate using liquid class properties.
1659
+
1660
+ Args:
1661
+ liquid_class: The liquid class to use for transfer properties.
1662
+ volume: Volume to transfer per well.
1663
+ source: List of source wells, with each well represented as a tuple of
1664
+ types.Location and WellCore.
1665
+ types.Location is only necessary for saving the last accessed location.
1666
+ dest: List of destination wells, with each well represented as a tuple of
1667
+ types.Location and WellCore.
1668
+ types.Location is only necessary for saving the last accessed location.
1669
+ new_tip: Whether the transfer should use a new tip 'once', 'always' or 'never'.
1670
+ 'never': the transfer will never pick up a new tip
1671
+ 'once': the transfer will pick up a new tip once at the start of transfer
1672
+ 'always': the transfer will pick up a new tip after every dispense
1673
+ tip_racks: List of tipracks that the transfer will pick up tips from, represented
1674
+ as tuples of types.Location and WellCore.
1675
+ starting_tip: The user-chosen starting tip to use when deciding what tip to pick
1676
+ up, if the user has set it.
1677
+ trash_location: The chosen trash container to drop tips in and dispose liquid in.
1678
+ return_tip: If `True`, return tips to the tip rack location they were picked up from,
1679
+ otherwise drop in `trash_location`
1680
+ keep_last_tip: When set to `True`, do not drop the final tip used in the consolidate.
1681
+ """
1682
+ if not tip_racks:
1683
+ raise RuntimeError(
1684
+ "No tipracks found for pipette in order to perform transfer"
1685
+ )
1686
+ # NOTE: Tip option of "always" in consolidate is equivalent to "after every dispense",
1687
+ # or more specifically, "before the next chunk of aspirates".
1688
+ assert new_tip in [
1689
+ TransferTipPolicyV2.NEVER,
1690
+ TransferTipPolicyV2.ONCE,
1691
+ TransferTipPolicyV2.ALWAYS,
1692
+ ]
1693
+ tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
1694
+ transfer_props = self._get_transfer_properties_for_tip_rack(
1695
+ liquid_class, tiprack_uri_for_transfer_props
1696
+ )
1697
+
1698
+ blow_out_properties = transfer_props.dispense.retract.blowout
1699
+ if (
1700
+ blow_out_properties.enabled
1701
+ and blow_out_properties.location == BlowoutLocation.SOURCE
1702
+ ):
1703
+ raise RuntimeError(
1704
+ 'Blowout location "source" incompatible with consolidate liquid.'
1705
+ ' Please choose "destination" or "trash".'
1706
+ )
1707
+
1708
+ # TODO: use the ID returned by load_liquid_class in command annotations
1709
+ self.load_liquid_class(
1710
+ name=liquid_class.name,
1711
+ transfer_properties=transfer_props,
1712
+ tiprack_uri=tiprack_uri_for_transfer_props,
1713
+ )
1714
+
1715
+ working_volume = self.get_working_volume_for_tip_rack(tip_racks[0][1])
1716
+
1717
+ source_per_volume_step = (
1718
+ tx_commons.get_sources_and_destinations_for_liquid_classes(
1719
+ volumes=[volume for _ in range(len(source))],
1720
+ max_volume=working_volume,
1721
+ targets=source,
1722
+ transfer_properties=transfer_props,
1723
+ )
1724
+ )
1725
+
1726
+ if new_tip in [TransferTipPolicyV2.ONCE, TransferTipPolicyV2.ALWAYS]:
1727
+ self._pick_up_tip_for_liquid_class(
1728
+ tip_racks, starting_tip, tiprack_uri_for_transfer_props
1729
+ )
1730
+
1731
+ aspirate_air_gap_by_volume = transfer_props.aspirate.retract.air_gap_by_volume
1732
+ tip_contents = [
1733
+ tx_comps_executor.LiquidAndAirGapPair(
1734
+ liquid=0,
1735
+ air_gap=0,
1736
+ )
1737
+ ]
1738
+ next_step_volume, next_source = next(source_per_volume_step)
1739
+ is_first_step = True
1740
+ is_last_step = False
1741
+ while not is_last_step:
1742
+ total_dispense_volume = 0.0
1743
+ vol_aspirate_combo = []
1744
+ air_gap = aspirate_air_gap_by_volume.get_for_volume(next_step_volume)
1745
+ # Take air gap into account because there will be a final air gap before the dispense
1746
+ while total_dispense_volume + next_step_volume <= working_volume - air_gap:
1747
+ total_dispense_volume += next_step_volume
1748
+ vol_aspirate_combo.append((next_step_volume, next_source))
1749
+ try:
1750
+ next_step_volume, next_source = next(source_per_volume_step)
1751
+ air_gap = aspirate_air_gap_by_volume.get_for_volume(
1752
+ next_step_volume + total_dispense_volume
1753
+ )
1754
+ except StopIteration:
1755
+ is_last_step = True
1756
+ break
1757
+
1758
+ if not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
1759
+ self._drop_tip_for_liquid_class(trash_location, return_tip)
1760
+ self._pick_up_tip_for_liquid_class(
1761
+ tip_racks, starting_tip, tiprack_uri_for_transfer_props
1762
+ )
1763
+ tip_contents = [
1764
+ tx_comps_executor.LiquidAndAirGapPair(
1765
+ liquid=0,
1766
+ air_gap=0,
1767
+ )
1768
+ ]
1769
+
1770
+ total_aspirated_volume = 0.0
1771
+ for step_num, (step_volume, step_source) in enumerate(vol_aspirate_combo):
1772
+ tip_contents = self.aspirate_liquid_class(
1773
+ volume=step_volume,
1774
+ source=step_source,
1775
+ transfer_properties=transfer_props,
1776
+ transfer_type=tx_comps_executor.TransferType.MANY_TO_ONE,
1777
+ tip_contents=tip_contents,
1778
+ volume_for_pipette_mode_configuration=(
1779
+ total_dispense_volume if step_num == 0 else None
1780
+ ),
1781
+ current_volume=total_aspirated_volume,
1782
+ )
1783
+ total_aspirated_volume += step_volume
1784
+ is_first_step = False
1785
+ tip_contents = self.dispense_liquid_class(
1786
+ volume=total_dispense_volume,
1787
+ dest=dest,
1788
+ source=None, # Cannot have source as location for blowout so hardcoded to None
1789
+ transfer_properties=transfer_props,
1790
+ transfer_type=tx_comps_executor.TransferType.MANY_TO_ONE,
1791
+ tip_contents=tip_contents,
1792
+ add_final_air_gap=(False if is_last_step and keep_last_tip else True),
1793
+ trash_location=trash_location,
1794
+ )
1795
+
1796
+ if not keep_last_tip:
1797
+ self._drop_tip_for_liquid_class(trash_location, return_tip)
1798
+
1799
+ def _get_location_and_well_core_from_next_tip_info(
1800
+ self,
1801
+ tip_info: NextTipInfo,
1802
+ tip_racks: List[Tuple[Location, LabwareCore]],
1803
+ ) -> _TipInfo:
1804
+ tiprack_labware_core = self._protocol_core._labware_cores_by_id[
1805
+ tip_info.labwareId
1806
+ ]
1807
+ tip_well = tiprack_labware_core.get_well_core(tip_info.tipStartingWell)
1808
+
1809
+ tiprack_loc = [
1810
+ loc for loc, lw_core in tip_racks if lw_core == tiprack_labware_core
1811
+ ]
1812
+
1813
+ return _TipInfo(
1814
+ Location(tip_well.get_top(0), tiprack_loc[0].labware),
1815
+ tiprack_labware_core.get_uri(),
1816
+ tip_well,
1817
+ )
1818
+
1819
+ def _get_transfer_properties_for_tip_rack(
1820
+ self, liquid_class: LiquidClass, tip_rack_uri: str
1821
+ ) -> TransferProperties:
1822
+ try:
1823
+ return liquid_class.get_for(
1824
+ pipette=self.get_pipette_name(), tip_rack=tip_rack_uri
1825
+ )
1826
+ except NoLiquidClassPropertyError:
1827
+ if self._protocol_core.robot_type == "OT-2 Standard":
1828
+ raise NoLiquidClassPropertyError(
1829
+ "Default liquid classes are not supported with OT-2 pipettes and tip racks."
1830
+ ) from None
1831
+ raise
1832
+
1833
+ def get_working_volume_for_tip_rack(self, tip_rack: LabwareCore) -> float:
1834
+ """Given a tip rack, return the maximum allowed volume for the pipette."""
1835
+ return min(
1836
+ self.get_max_volume(),
1837
+ self._engine_client.state.geometry.get_nominal_tip_geometry(
1838
+ pipette_id=self.pipette_id,
1839
+ labware_id=tip_rack.labware_id,
1840
+ well_name=None,
1841
+ ).volume,
1842
+ )
1843
+
1844
+ def _pick_up_tip_for_liquid_class(
1845
+ self,
1846
+ tip_racks: List[Tuple[Location, LabwareCore]],
1847
+ starting_tip: Optional[WellCore],
1848
+ tiprack_uri_for_transfer_props: str,
1849
+ ) -> None:
1850
+ """Resolve next tip and pick it up, for use in liquid class transfer code."""
1851
+ next_tip = self.get_next_tip(
1852
+ tip_racks=[core for loc, core in tip_racks],
1853
+ starting_well=starting_tip,
1854
+ )
1855
+ if next_tip is None:
1856
+ raise RuntimeError(
1857
+ f"No tip available among the tipracks assigned for {self.get_pipette_name()}:"
1858
+ f" {[f'{tip_rack[1].get_display_name()} in {tip_rack[1].get_deck_slot()}' for tip_rack in tip_racks]}"
1859
+ )
1860
+ (
1861
+ tiprack_loc,
1862
+ tiprack_uri,
1863
+ tip_well,
1864
+ ) = self._get_location_and_well_core_from_next_tip_info(next_tip, tip_racks)
1865
+ if tiprack_uri != tiprack_uri_for_transfer_props:
1866
+ raise RuntimeError(
1867
+ f"Tiprack {tiprack_uri} does not match the tiprack designated "
1868
+ f"for this transfer- {tiprack_uri_for_transfer_props}."
1869
+ )
1870
+ self.pick_up_tip(
1871
+ location=tiprack_loc,
1872
+ well_core=tip_well,
1873
+ presses=None,
1874
+ increment=None,
1875
+ )
1876
+
1877
+ def _drop_tip_for_liquid_class(
1878
+ self,
1879
+ trash_location: Union[Location, TrashBin, WasteChute],
1880
+ return_tip: bool,
1881
+ ) -> None:
1882
+ """Drop or return tip for usage in liquid class transfers."""
1883
+ if return_tip:
1884
+ last_tip = self.get_tip_origin()
1885
+ assert last_tip is not None
1886
+ _, tip_well = last_tip
1887
+ self.drop_tip(
1888
+ location=None,
1889
+ well_core=tip_well,
1890
+ home_after=False,
1891
+ alternate_drop_location=False,
1892
+ )
1893
+ elif isinstance(trash_location, (TrashBin, WasteChute)):
1894
+ self.drop_tip_in_disposal_location(
1895
+ disposal_location=trash_location,
1896
+ home_after=False,
1897
+ alternate_tip_drop=True,
1898
+ )
1899
+ elif isinstance(trash_location, Location):
1900
+ self.drop_tip(
1901
+ location=trash_location,
1902
+ well_core=trash_location.labware.as_well()._core, # type: ignore[arg-type]
1903
+ home_after=False,
1904
+ alternate_drop_location=True,
1905
+ )
1906
+
1907
+ def aspirate_liquid_class(
1908
+ self,
1909
+ volume: float,
1910
+ source: Tuple[Location, WellCore],
1911
+ transfer_properties: TransferProperties,
1912
+ transfer_type: tx_comps_executor.TransferType,
1913
+ tip_contents: List[tx_comps_executor.LiquidAndAirGapPair],
1914
+ volume_for_pipette_mode_configuration: Optional[float],
1915
+ conditioning_volume: Optional[float] = None,
1916
+ current_volume: float = 0.0,
1917
+ ) -> List[tx_comps_executor.LiquidAndAirGapPair]:
1918
+ """Execute aspiration steps.
1919
+
1920
+ 1. Submerge
1921
+ 2. Mix
1922
+ 3. pre-wet
1923
+ 4. Aspirate
1924
+ 5. Delay- wait inside the liquid
1925
+ 6. Aspirate retract
1926
+
1927
+ Return: List of liquid and air gap pairs in tip.
1928
+ """
1929
+ aspirate_props = transfer_properties.aspirate
1930
+ volume_for_air_gap = aspirate_props.retract.air_gap_by_volume.get_for_volume(
1931
+ volume + current_volume
1932
+ )
1933
+ tx_commons.check_valid_liquid_class_volume_parameters(
1934
+ aspirate_volume=volume,
1935
+ air_gap=volume_for_air_gap if conditioning_volume is None else 0,
1936
+ max_volume=self.get_working_volume(),
1937
+ current_volume=current_volume,
1938
+ )
1939
+ source_loc, source_well = source
1940
+ last_liquid_and_airgap_in_tip = (
1941
+ deepcopy(tip_contents[-1]) # don't modify caller's object
1942
+ if tip_contents
1943
+ else tx_comps_executor.LiquidAndAirGapPair(
1944
+ liquid=0,
1945
+ air_gap=0,
1946
+ )
1947
+ )
1948
+ if volume_for_pipette_mode_configuration is not None:
1949
+ prep_location = Location(
1950
+ point=source_well.get_top(LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP.z),
1951
+ labware=source_loc.labware,
1952
+ )
1953
+ self.move_to(
1954
+ location=prep_location,
1955
+ well_core=source_well,
1956
+ force_direct=False,
1957
+ minimum_z_height=None,
1958
+ speed=None,
1959
+ )
1960
+ self.remove_air_gap_during_transfer_with_liquid_class(
1961
+ last_air_gap=last_liquid_and_airgap_in_tip.air_gap,
1962
+ dispense_props=transfer_properties.dispense,
1963
+ location=prep_location,
1964
+ )
1965
+ last_liquid_and_airgap_in_tip.air_gap = 0
1966
+ # TODO: do volume configuration + prepare for aspirate only if the mode needs to be changed
1967
+ self.configure_for_volume(volume_for_pipette_mode_configuration)
1968
+ self.prepare_to_aspirate()
1969
+
1970
+ aspirate_point = (
1971
+ tx_comps_executor.absolute_point_from_position_reference_and_offset(
1972
+ well=source_well,
1973
+ well_volume_difference=-volume,
1974
+ position_reference=aspirate_props.aspirate_position.position_reference,
1975
+ offset=aspirate_props.aspirate_position.offset,
1976
+ mount=self.get_mount(),
1977
+ )
1978
+ )
1979
+ aspirate_location = Location(aspirate_point, labware=source_loc.labware)
1980
+
1981
+ components_executor = tx_comps_executor.TransferComponentsExecutor(
1982
+ instrument_core=self,
1983
+ transfer_properties=transfer_properties,
1984
+ target_location=aspirate_location,
1985
+ target_well=source_well,
1986
+ transfer_type=transfer_type,
1987
+ tip_state=tx_comps_executor.TipState(
1988
+ last_liquid_and_air_gap_in_tip=last_liquid_and_airgap_in_tip
1989
+ ),
1990
+ )
1991
+ components_executor.submerge(
1992
+ submerge_properties=aspirate_props.submerge, post_submerge_action="aspirate"
1993
+ )
1994
+ # Do not do a pre-aspirate mix or pre-wet if consolidating
1995
+ if transfer_type != tx_comps_executor.TransferType.MANY_TO_ONE:
1996
+ # TODO: check if we want to do a mix only once when we're splitting a transfer
1997
+ # and coming back to the source multiple times.
1998
+ # We will have to do pre-wet always even for split volumes
1999
+ components_executor.mix(
2000
+ mix_properties=aspirate_props.mix, last_dispense_push_out=False
2001
+ )
2002
+ # TODO: check if pre-wet needs to be enabled for first well of consolidate
2003
+ components_executor.pre_wet(
2004
+ volume=volume,
2005
+ )
2006
+ components_executor.aspirate_and_wait(volume=volume)
2007
+ if (
2008
+ transfer_type == tx_comps_executor.TransferType.ONE_TO_MANY
2009
+ and conditioning_volume not in [None, 0.0]
2010
+ and transfer_properties.multi_dispense is not None
2011
+ ):
2012
+ # Dispense the conditioning volume
2013
+ components_executor.dispense_and_wait(
2014
+ dispense_properties=transfer_properties.multi_dispense,
2015
+ volume=conditioning_volume or 0.0,
2016
+ push_out_override=0,
2017
+ )
2018
+ components_executor.retract_after_aspiration(
2019
+ volume=volume, add_air_gap=False
2020
+ )
2021
+ else:
2022
+ components_executor.retract_after_aspiration(
2023
+ volume=volume, add_air_gap=True
2024
+ )
2025
+
2026
+ # return copy of tip_contents with last entry replaced by tip state from executor
2027
+ last_contents = components_executor.tip_state.last_liquid_and_air_gap_in_tip
2028
+ new_tip_contents = tip_contents[0:-1] + [last_contents]
2029
+ return new_tip_contents
2030
+
2031
+ def remove_air_gap_during_transfer_with_liquid_class(
2032
+ self,
2033
+ last_air_gap: float,
2034
+ dispense_props: SingleDispenseProperties,
2035
+ location: Union[Location, TrashBin, WasteChute],
2036
+ ) -> None:
2037
+ """Remove an air gap that was previously added during a transfer."""
2038
+ if last_air_gap == 0:
2039
+ return
2040
+ current_vol = self.get_current_volume()
2041
+ check_current_volume_before_dispensing(
2042
+ current_volume=current_vol, dispense_volume=last_air_gap
2043
+ )
2044
+ correction_volume = dispense_props.correction_by_volume.get_for_volume(
2045
+ current_vol - last_air_gap
2046
+ )
2047
+ # The minimum flow rate should be air_gap_volume per second
2048
+ flow_rate = max(
2049
+ dispense_props.flow_rate_by_volume.get_for_volume(last_air_gap),
2050
+ last_air_gap,
2051
+ )
2052
+ self.dispense(
2053
+ location=location,
2054
+ well_core=None,
2055
+ volume=last_air_gap,
2056
+ rate=1,
2057
+ flow_rate=flow_rate,
2058
+ in_place=True,
2059
+ push_out=0,
2060
+ correction_volume=correction_volume,
2061
+ )
2062
+ dispense_delay = dispense_props.delay
2063
+ if dispense_delay.enabled and dispense_delay.duration:
2064
+ self.delay(dispense_delay.duration)
2065
+
2066
+ def dispense_liquid_class(
2067
+ self,
2068
+ volume: float,
2069
+ dest: Union[Tuple[Location, WellCore], TrashBin, WasteChute],
2070
+ source: Optional[Tuple[Location, WellCore]],
2071
+ transfer_properties: TransferProperties,
2072
+ transfer_type: tx_comps_executor.TransferType,
2073
+ tip_contents: List[tx_comps_executor.LiquidAndAirGapPair],
2074
+ add_final_air_gap: bool,
2075
+ trash_location: Union[Location, TrashBin, WasteChute],
2076
+ ) -> List[tx_comps_executor.LiquidAndAirGapPair]:
2077
+ """Execute single-dispense steps.
2078
+ 1. Move pipette to the ‘submerge’ position with normal speed.
2079
+ - The pipette will move in an arc- move to max z height of labware
2080
+ (if asp & disp are in same labware)
2081
+ or max z height of all labware (if asp & disp are in separate labware)
2082
+ 2. Air gap removal:
2083
+ - If dispense location is above the meniscus, DO NOT remove air gap
2084
+ (it will be dispensed along with rest of the liquid later).
2085
+ All other scenarios, remove the air gap by doing a dispense
2086
+ - Flow rate = min(dispenseFlowRate, (airGapByVolume)/sec)
2087
+ - Use the post-dispense delay
2088
+ 4. Move to the dispense position at the specified ‘submerge’ speed
2089
+ (even if we might not be moving into the liquid)
2090
+ - Do a delay (submerge delay)
2091
+ 6. Dispense:
2092
+ - Dispense at the specified flow rate.
2093
+ - Do a push out as specified ONLY IF there is no mix following the dispense AND the tip is empty.
2094
+ Volume for push out is the volume being dispensed. So if we are dispensing 50uL, use pushOutByVolume[50] as push out volume.
2095
+ 7. Delay
2096
+ 8. Mix using the same flow rate and delays as specified for asp+disp,
2097
+ with the volume and the number of repetitions specified. Use the delays in asp & disp.
2098
+ - If the dispense position is outside the liquid, then raise error if mix is enabled.
2099
+ Can only be checked if using liquid level detection/ meniscus-based positioning.
2100
+ - If the user wants to perform a mix then they should specify a dispense position that’s inside the liquid OR do mix() on the wells after transfer.
2101
+ - Do push out at the last dispense.
2102
+ 9. Retract
2103
+
2104
+ Return:
2105
+ List of liquid and air gap pairs in tip.
2106
+ """
2107
+ dispense_props = transfer_properties.dispense
2108
+ dispense_location: Union[Location, TrashBin, WasteChute]
2109
+ if isinstance(dest, tuple):
2110
+ dest_loc, dest_well = dest
2111
+ dispense_point = tx_comps_executor.absolute_point_from_position_reference_and_offset(
2112
+ well=dest_well,
2113
+ well_volume_difference=volume,
2114
+ position_reference=dispense_props.dispense_position.position_reference,
2115
+ offset=dispense_props.dispense_position.offset,
2116
+ mount=self.get_mount(),
2117
+ )
2118
+ dispense_location = Location(dispense_point, labware=dest_loc.labware)
2119
+ else:
2120
+ dispense_location = dest
2121
+ dest_well = None
2122
+
2123
+ last_liquid_and_airgap_in_tip = (
2124
+ tip_contents[-1]
2125
+ if tip_contents
2126
+ else tx_comps_executor.LiquidAndAirGapPair(
2127
+ liquid=0,
2128
+ air_gap=0,
2129
+ )
2130
+ )
2131
+ components_executor = tx_comps_executor.TransferComponentsExecutor(
2132
+ instrument_core=self,
2133
+ transfer_properties=transfer_properties,
2134
+ target_location=dispense_location,
2135
+ target_well=dest_well,
2136
+ transfer_type=transfer_type,
2137
+ tip_state=tx_comps_executor.TipState(
2138
+ last_liquid_and_air_gap_in_tip=last_liquid_and_airgap_in_tip
2139
+ ),
2140
+ )
2141
+ components_executor.submerge(
2142
+ submerge_properties=dispense_props.submerge, post_submerge_action="dispense"
2143
+ )
2144
+ push_out_vol = (
2145
+ 0.0
2146
+ if dispense_props.mix.enabled
2147
+ else dispense_props.push_out_by_volume.get_for_volume(volume)
2148
+ )
2149
+ components_executor.dispense_and_wait(
2150
+ dispense_properties=dispense_props,
2151
+ volume=volume,
2152
+ push_out_override=push_out_vol,
2153
+ )
2154
+ components_executor.mix(
2155
+ mix_properties=dispense_props.mix,
2156
+ last_dispense_push_out=True,
2157
+ )
2158
+ components_executor.retract_after_dispensing(
2159
+ trash_location=trash_location,
2160
+ source_location=source[0] if source else None,
2161
+ source_well=source[1] if source else None,
2162
+ add_final_air_gap=add_final_air_gap,
2163
+ )
2164
+ last_contents = components_executor.tip_state.last_liquid_and_air_gap_in_tip
2165
+ new_tip_contents = tip_contents[0:-1] + [last_contents]
2166
+ return new_tip_contents
2167
+
2168
+ def dispense_liquid_class_during_multi_dispense(
2169
+ self,
2170
+ volume: float,
2171
+ dest: Tuple[Location, WellCore],
2172
+ source: Optional[Tuple[Location, WellCore]],
2173
+ transfer_properties: TransferProperties,
2174
+ transfer_type: tx_comps_executor.TransferType,
2175
+ tip_contents: List[tx_comps_executor.LiquidAndAirGapPair],
2176
+ add_final_air_gap: bool,
2177
+ trash_location: Union[Location, TrashBin, WasteChute],
2178
+ conditioning_volume: float,
2179
+ disposal_volume: float,
2180
+ is_last_dispense_in_tip: bool,
2181
+ ) -> List[tx_comps_executor.LiquidAndAirGapPair]:
2182
+ """Execute a dispense step that's part of a multi-dispense.
2183
+
2184
+ This executes a dispense step very similar to a single dispense except that:
2185
+ - it uses the multi-dispense properties from the liquid class
2186
+ - handles push-out based on disposal volume in addition to the existing conditions
2187
+ - delegates the retraction steps to a different, multi-dispense retract function
2188
+
2189
+ Return:
2190
+ List of liquid and air gap pairs in tip.
2191
+ """
2192
+ assert transfer_properties.multi_dispense is not None
2193
+ dispense_props = transfer_properties.multi_dispense
2194
+
2195
+ dest_loc, dest_well = dest
2196
+ dispense_point = (
2197
+ tx_comps_executor.absolute_point_from_position_reference_and_offset(
2198
+ well=dest_well,
2199
+ well_volume_difference=volume,
2200
+ position_reference=dispense_props.dispense_position.position_reference,
2201
+ offset=dispense_props.dispense_position.offset,
2202
+ mount=self.get_mount(),
2203
+ )
2204
+ )
2205
+ dispense_location = Location(dispense_point, labware=dest_loc.labware)
2206
+ last_liquid_and_airgap_in_tip = (
2207
+ tip_contents[-1]
2208
+ if tip_contents
2209
+ else tx_comps_executor.LiquidAndAirGapPair(
2210
+ liquid=0,
2211
+ air_gap=0,
2212
+ )
2213
+ )
2214
+ components_executor = tx_comps_executor.TransferComponentsExecutor(
2215
+ instrument_core=self,
2216
+ transfer_properties=transfer_properties,
2217
+ target_location=dispense_location,
2218
+ target_well=dest_well,
2219
+ transfer_type=transfer_type,
2220
+ tip_state=tx_comps_executor.TipState(
2221
+ last_liquid_and_air_gap_in_tip=last_liquid_and_airgap_in_tip
2222
+ ),
2223
+ )
2224
+ components_executor.submerge(
2225
+ submerge_properties=dispense_props.submerge, post_submerge_action="dispense"
2226
+ )
2227
+ is_last_dispense_without_disposal_vol = (
2228
+ disposal_volume == 0 and is_last_dispense_in_tip
2229
+ )
2230
+ push_out_vol = (
2231
+ # TODO (spp): verify if it's okay to use push_out_by_volume of single dispense
2232
+ transfer_properties.dispense.push_out_by_volume.get_for_volume(volume)
2233
+ if is_last_dispense_without_disposal_vol
2234
+ else 0.0
2235
+ )
2236
+
2237
+ components_executor.dispense_and_wait(
2238
+ dispense_properties=dispense_props,
2239
+ volume=volume,
2240
+ push_out_override=push_out_vol,
2241
+ )
2242
+ components_executor.retract_during_multi_dispensing(
2243
+ trash_location=trash_location,
2244
+ source_location=source[0] if source else None,
2245
+ source_well=source[1] if source else None,
2246
+ conditioning_volume=conditioning_volume,
2247
+ add_final_air_gap=add_final_air_gap,
2248
+ is_last_retract=is_last_dispense_in_tip,
2249
+ )
2250
+ last_contents = components_executor.tip_state.last_liquid_and_air_gap_in_tip
2251
+ new_tip_contents = tip_contents[0:-1] + [last_contents]
2252
+ return new_tip_contents
2253
+
2254
+ def retract(self) -> None:
2255
+ """Retract this instrument to the top of the gantry."""
2256
+ z_axis = self._engine_client.state.pipettes.get_z_axis(self._pipette_id)
2257
+ self._engine_client.execute_command(cmd.HomeParams(axes=[z_axis]))
2258
+
2259
+ def _pressure_supported_by_pipette(self) -> bool:
2260
+ return self._engine_client.state.pipettes.get_pipette_supports_pressure(
2261
+ self.pipette_id
2262
+ )
2263
+
2264
+ def detect_liquid_presence(self, well_core: WellCore, loc: Location) -> bool:
2265
+ labware_id = well_core.labware_id
2266
+ well_name = well_core.get_name()
2267
+ offset = LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP
2268
+ well_location = WellLocation(
2269
+ origin=WellOrigin.TOP, offset=WellOffset(x=offset.x, y=offset.y, z=offset.z)
2270
+ )
2271
+
2272
+ # The error handling here is a bit nuanced and also a bit broken:
2273
+ #
2274
+ # - If the hardware detects liquid, the `tryLiquidProbe` engine command will
2275
+ # succeed and return a height, which we'll convert to a `True` return.
2276
+ # Okay so far.
2277
+ #
2278
+ # - If the hardware detects no liquid, the `tryLiquidProbe` engine command will
2279
+ # succeed and return `None`, which we'll convert to a `False` return.
2280
+ # Still okay so far.
2281
+ #
2282
+ # - If there is any other error within the `tryLiquidProbe` command, things get
2283
+ # messy. It may kick the run into recovery mode. At that point, all bets are
2284
+ # off--we lose our guarantee of having a `tryLiquidProbe` command whose
2285
+ # `result` we can inspect. We don't know how to deal with that here, so we
2286
+ # currently propagate the exception up, which will quickly kill the protocol,
2287
+ # after a potential split second of recovery mode. It's unclear what would
2288
+ # be good user-facing behavior here, but it's unfortunate to kill the protocol
2289
+ # for an error that the engine thinks should be recoverable.
2290
+ result = self._engine_client.execute_command_without_recovery(
2291
+ cmd.TryLiquidProbeParams(
2292
+ labwareId=labware_id,
2293
+ wellName=well_name,
2294
+ wellLocation=well_location,
2295
+ pipetteId=self.pipette_id,
2296
+ )
2297
+ )
2298
+
2299
+ self._protocol_core.set_last_location(location=loc, mount=self.get_mount())
2300
+
2301
+ return result.z_position is not None
2302
+
2303
+ def get_minimum_liquid_sense_height(self) -> float:
2304
+ attached_tip = self._engine_client.state.pipettes.get_attached_tip(
2305
+ self._pipette_id
2306
+ )
2307
+ if attached_tip:
2308
+ tip_volume = attached_tip.volume
2309
+ else:
2310
+ raise TipNotAttachedError(
2311
+ "Need to have a tip attached for liquid-sense operations."
2312
+ )
2313
+ lld_settings = self._engine_client.state.pipettes.get_pipette_lld_settings(
2314
+ pipette_id=self.pipette_id
2315
+ )
2316
+ if lld_settings:
2317
+ lld_min_height_for_tip_attached = lld_settings[f"t{tip_volume}"][
2318
+ "minHeight"
2319
+ ]
2320
+ return lld_min_height_for_tip_attached
2321
+ else:
2322
+ raise ValueError("liquid-level detection settings not found.")
2323
+
2324
+ def liquid_probe_with_recovery(self, well_core: WellCore, loc: Location) -> None:
2325
+ labware_id = well_core.labware_id
2326
+ well_name = well_core.get_name()
2327
+ offset = LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP
2328
+ well_location = WellLocation(
2329
+ origin=WellOrigin.TOP, offset=WellOffset(x=offset.x, y=offset.y, z=offset.z)
2330
+ )
2331
+ pipette_movement_conflict.check_safe_for_pipette_movement(
2332
+ engine_state=self._engine_client.state,
2333
+ pipette_id=self._pipette_id,
2334
+ labware_id=labware_id,
2335
+ well_name=well_name,
2336
+ well_location=well_location,
2337
+ )
2338
+ self._engine_client.execute_command(
2339
+ cmd.LiquidProbeParams(
2340
+ labwareId=labware_id,
2341
+ wellName=well_name,
2342
+ wellLocation=well_location,
2343
+ pipetteId=self.pipette_id,
2344
+ )
2345
+ )
2346
+
2347
+ self._protocol_core.set_last_location(location=loc, mount=self.get_mount())
2348
+
2349
+ def liquid_probe_without_recovery(
2350
+ self, well_core: WellCore, loc: Location
2351
+ ) -> LiquidTrackingType:
2352
+ labware_id = well_core.labware_id
2353
+ well_name = well_core.get_name()
2354
+ offset = LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP
2355
+ well_location = WellLocation(
2356
+ origin=WellOrigin.TOP, offset=WellOffset(x=offset.x, y=offset.y, z=offset.z)
2357
+ )
2358
+ pipette_movement_conflict.check_safe_for_pipette_movement(
2359
+ engine_state=self._engine_client.state,
2360
+ pipette_id=self._pipette_id,
2361
+ labware_id=labware_id,
2362
+ well_name=well_name,
2363
+ well_location=well_location,
2364
+ )
2365
+ result = self._engine_client.execute_command_without_recovery(
2366
+ cmd.LiquidProbeParams(
2367
+ labwareId=labware_id,
2368
+ wellName=well_name,
2369
+ wellLocation=well_location,
2370
+ pipetteId=self.pipette_id,
2371
+ )
2372
+ )
2373
+
2374
+ self._protocol_core.set_last_location(location=loc, mount=self.get_mount())
2375
+ return result.z_position
2376
+
2377
+ def nozzle_configuration_valid_for_lld(self) -> bool:
2378
+ """Check if the nozzle configuration currently supports LLD."""
2379
+ return self._engine_client.state.pipettes.get_nozzle_configuration_supports_lld(
2380
+ self.pipette_id
2381
+ )
2382
+
2383
+ def delay(self, seconds: float) -> None:
2384
+ """Call a protocol delay."""
2385
+ self._protocol_core.delay(seconds=seconds, msg=None)
2386
+
2387
+
2388
+ class _TipInfo(NamedTuple):
2389
+ tiprack_location: Location
2390
+ tiprack_uri: str
2391
+ tip_well: WellCore