opentrons 8.6.0a1__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 (600) 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 +501 -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 +183 -0
  45. opentrons/drivers/asyncio/communication/errors.py +88 -0
  46. opentrons/drivers/asyncio/communication/serial_connection.py +552 -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/simulator_setup.py +260 -0
  200. opentrons/hardware_control/thread_manager.py +431 -0
  201. opentrons/hardware_control/threaded_async_lock.py +97 -0
  202. opentrons/hardware_control/types.py +792 -0
  203. opentrons/hardware_control/util.py +234 -0
  204. opentrons/legacy_broker.py +53 -0
  205. opentrons/legacy_commands/__init__.py +1 -0
  206. opentrons/legacy_commands/commands.py +483 -0
  207. opentrons/legacy_commands/helpers.py +153 -0
  208. opentrons/legacy_commands/module_commands.py +215 -0
  209. opentrons/legacy_commands/protocol_commands.py +54 -0
  210. opentrons/legacy_commands/publisher.py +155 -0
  211. opentrons/legacy_commands/robot_commands.py +51 -0
  212. opentrons/legacy_commands/types.py +1115 -0
  213. opentrons/motion_planning/__init__.py +32 -0
  214. opentrons/motion_planning/adjacent_slots_getters.py +168 -0
  215. opentrons/motion_planning/deck_conflict.py +396 -0
  216. opentrons/motion_planning/errors.py +35 -0
  217. opentrons/motion_planning/types.py +42 -0
  218. opentrons/motion_planning/waypoints.py +218 -0
  219. opentrons/ordered_set.py +138 -0
  220. opentrons/protocol_api/__init__.py +105 -0
  221. opentrons/protocol_api/_liquid.py +157 -0
  222. opentrons/protocol_api/_liquid_properties.py +814 -0
  223. opentrons/protocol_api/_nozzle_layout.py +31 -0
  224. opentrons/protocol_api/_parameter_context.py +300 -0
  225. opentrons/protocol_api/_parameters.py +31 -0
  226. opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
  227. opentrons/protocol_api/_types.py +43 -0
  228. opentrons/protocol_api/config.py +23 -0
  229. opentrons/protocol_api/core/__init__.py +23 -0
  230. opentrons/protocol_api/core/common.py +33 -0
  231. opentrons/protocol_api/core/core_map.py +74 -0
  232. opentrons/protocol_api/core/engine/__init__.py +22 -0
  233. opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
  234. opentrons/protocol_api/core/engine/deck_conflict.py +348 -0
  235. opentrons/protocol_api/core/engine/exceptions.py +19 -0
  236. opentrons/protocol_api/core/engine/instrument.py +2391 -0
  237. opentrons/protocol_api/core/engine/labware.py +238 -0
  238. opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
  239. opentrons/protocol_api/core/engine/module_core.py +1025 -0
  240. opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
  241. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
  242. opentrons/protocol_api/core/engine/point_calculations.py +64 -0
  243. opentrons/protocol_api/core/engine/protocol.py +1153 -0
  244. opentrons/protocol_api/core/engine/robot.py +139 -0
  245. opentrons/protocol_api/core/engine/stringify.py +74 -0
  246. opentrons/protocol_api/core/engine/transfer_components_executor.py +990 -0
  247. opentrons/protocol_api/core/engine/well.py +241 -0
  248. opentrons/protocol_api/core/instrument.py +459 -0
  249. opentrons/protocol_api/core/labware.py +151 -0
  250. opentrons/protocol_api/core/legacy/__init__.py +11 -0
  251. opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
  252. opentrons/protocol_api/core/legacy/deck.py +369 -0
  253. opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
  254. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
  255. opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
  256. opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
  257. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
  258. opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
  259. opentrons/protocol_api/core/legacy/load_info.py +67 -0
  260. opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
  261. opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
  262. opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
  263. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
  264. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
  265. opentrons/protocol_api/core/module.py +484 -0
  266. opentrons/protocol_api/core/protocol.py +311 -0
  267. opentrons/protocol_api/core/robot.py +51 -0
  268. opentrons/protocol_api/core/well.py +116 -0
  269. opentrons/protocol_api/core/well_grid.py +45 -0
  270. opentrons/protocol_api/create_protocol_context.py +177 -0
  271. opentrons/protocol_api/deck.py +223 -0
  272. opentrons/protocol_api/disposal_locations.py +244 -0
  273. opentrons/protocol_api/instrument_context.py +3212 -0
  274. opentrons/protocol_api/labware.py +1579 -0
  275. opentrons/protocol_api/module_contexts.py +1425 -0
  276. opentrons/protocol_api/module_validation_and_errors.py +61 -0
  277. opentrons/protocol_api/protocol_context.py +1688 -0
  278. opentrons/protocol_api/robot_context.py +303 -0
  279. opentrons/protocol_api/validation.py +761 -0
  280. opentrons/protocol_engine/__init__.py +155 -0
  281. opentrons/protocol_engine/actions/__init__.py +65 -0
  282. opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
  283. opentrons/protocol_engine/actions/action_handler.py +13 -0
  284. opentrons/protocol_engine/actions/actions.py +302 -0
  285. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  286. opentrons/protocol_engine/clients/__init__.py +5 -0
  287. opentrons/protocol_engine/clients/sync_client.py +174 -0
  288. opentrons/protocol_engine/clients/transports.py +197 -0
  289. opentrons/protocol_engine/commands/__init__.py +757 -0
  290. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
  291. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
  292. opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
  293. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
  294. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
  295. opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
  296. opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
  297. opentrons/protocol_engine/commands/aspirate.py +244 -0
  298. opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
  299. opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
  300. opentrons/protocol_engine/commands/blow_out.py +146 -0
  301. opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
  302. opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
  303. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
  304. opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
  305. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
  306. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
  307. opentrons/protocol_engine/commands/command.py +308 -0
  308. opentrons/protocol_engine/commands/command_unions.py +974 -0
  309. opentrons/protocol_engine/commands/comment.py +57 -0
  310. opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
  311. opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
  312. opentrons/protocol_engine/commands/custom.py +67 -0
  313. opentrons/protocol_engine/commands/dispense.py +194 -0
  314. opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
  315. opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
  316. opentrons/protocol_engine/commands/drop_tip.py +232 -0
  317. opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
  318. opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
  319. opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
  320. opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
  321. opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
  322. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
  323. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
  324. opentrons/protocol_engine/commands/flex_stacker/store.py +326 -0
  325. opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
  326. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  327. opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
  328. opentrons/protocol_engine/commands/hash_command_params.py +38 -0
  329. opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
  330. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
  331. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
  332. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
  333. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
  334. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
  335. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
  336. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
  337. opentrons/protocol_engine/commands/home.py +100 -0
  338. opentrons/protocol_engine/commands/identify_module.py +86 -0
  339. opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
  340. opentrons/protocol_engine/commands/liquid_probe.py +464 -0
  341. opentrons/protocol_engine/commands/load_labware.py +210 -0
  342. opentrons/protocol_engine/commands/load_lid.py +154 -0
  343. opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
  344. opentrons/protocol_engine/commands/load_liquid.py +95 -0
  345. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  346. opentrons/protocol_engine/commands/load_module.py +223 -0
  347. opentrons/protocol_engine/commands/load_pipette.py +167 -0
  348. opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
  349. opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
  350. opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
  351. opentrons/protocol_engine/commands/move_labware.py +546 -0
  352. opentrons/protocol_engine/commands/move_relative.py +102 -0
  353. opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
  354. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
  355. opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
  356. opentrons/protocol_engine/commands/move_to_well.py +119 -0
  357. opentrons/protocol_engine/commands/movement_common.py +338 -0
  358. opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
  359. opentrons/protocol_engine/commands/pipetting_common.py +443 -0
  360. opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
  361. opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
  362. opentrons/protocol_engine/commands/reload_labware.py +90 -0
  363. opentrons/protocol_engine/commands/retract_axis.py +75 -0
  364. opentrons/protocol_engine/commands/robot/__init__.py +70 -0
  365. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -0
  366. opentrons/protocol_engine/commands/robot/common.py +18 -0
  367. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  368. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  369. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  370. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +86 -0
  371. opentrons/protocol_engine/commands/save_position.py +109 -0
  372. opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
  373. opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
  374. opentrons/protocol_engine/commands/set_status_bar.py +89 -0
  375. opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
  376. opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
  377. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
  378. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
  379. opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
  380. opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
  381. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
  382. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
  383. opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
  384. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
  385. opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
  386. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
  387. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
  388. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
  389. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
  390. opentrons/protocol_engine/commands/touch_tip.py +189 -0
  391. opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
  392. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
  393. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
  394. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
  395. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
  396. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
  397. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
  398. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
  399. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
  400. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
  401. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
  402. opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
  403. opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
  404. opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
  405. opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
  406. opentrons/protocol_engine/create_protocol_engine.py +193 -0
  407. opentrons/protocol_engine/engine_support.py +28 -0
  408. opentrons/protocol_engine/error_recovery_policy.py +81 -0
  409. opentrons/protocol_engine/errors/__init__.py +191 -0
  410. opentrons/protocol_engine/errors/error_occurrence.py +182 -0
  411. opentrons/protocol_engine/errors/exceptions.py +1308 -0
  412. opentrons/protocol_engine/execution/__init__.py +50 -0
  413. opentrons/protocol_engine/execution/command_executor.py +216 -0
  414. opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
  415. opentrons/protocol_engine/execution/door_watcher.py +119 -0
  416. opentrons/protocol_engine/execution/equipment.py +819 -0
  417. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  418. opentrons/protocol_engine/execution/gantry_mover.py +686 -0
  419. opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
  420. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
  421. opentrons/protocol_engine/execution/labware_movement.py +297 -0
  422. opentrons/protocol_engine/execution/movement.py +349 -0
  423. opentrons/protocol_engine/execution/pipetting.py +607 -0
  424. opentrons/protocol_engine/execution/queue_worker.py +86 -0
  425. opentrons/protocol_engine/execution/rail_lights.py +25 -0
  426. opentrons/protocol_engine/execution/run_control.py +33 -0
  427. opentrons/protocol_engine/execution/status_bar.py +34 -0
  428. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
  429. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
  430. opentrons/protocol_engine/execution/tip_handler.py +550 -0
  431. opentrons/protocol_engine/labware_offset_standardization.py +194 -0
  432. opentrons/protocol_engine/notes/__init__.py +17 -0
  433. opentrons/protocol_engine/notes/notes.py +59 -0
  434. opentrons/protocol_engine/plugins.py +104 -0
  435. opentrons/protocol_engine/protocol_engine.py +683 -0
  436. opentrons/protocol_engine/resources/__init__.py +26 -0
  437. opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
  438. opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
  439. opentrons/protocol_engine/resources/file_provider.py +161 -0
  440. opentrons/protocol_engine/resources/fixture_validation.py +58 -0
  441. opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
  442. opentrons/protocol_engine/resources/labware_validation.py +73 -0
  443. opentrons/protocol_engine/resources/model_utils.py +32 -0
  444. opentrons/protocol_engine/resources/module_data_provider.py +44 -0
  445. opentrons/protocol_engine/resources/ot3_validation.py +21 -0
  446. opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
  447. opentrons/protocol_engine/slot_standardization.py +128 -0
  448. opentrons/protocol_engine/state/__init__.py +1 -0
  449. opentrons/protocol_engine/state/_abstract_store.py +27 -0
  450. opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
  451. opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
  452. opentrons/protocol_engine/state/_move_types.py +83 -0
  453. opentrons/protocol_engine/state/_well_math.py +193 -0
  454. opentrons/protocol_engine/state/addressable_areas.py +699 -0
  455. opentrons/protocol_engine/state/command_history.py +309 -0
  456. opentrons/protocol_engine/state/commands.py +1158 -0
  457. opentrons/protocol_engine/state/config.py +39 -0
  458. opentrons/protocol_engine/state/files.py +57 -0
  459. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  460. opentrons/protocol_engine/state/geometry.py +2359 -0
  461. opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
  462. opentrons/protocol_engine/state/labware.py +1459 -0
  463. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  464. opentrons/protocol_engine/state/liquids.py +73 -0
  465. opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
  466. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
  467. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
  468. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
  469. opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
  470. opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
  471. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
  472. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
  473. opentrons/protocol_engine/state/modules.py +1500 -0
  474. opentrons/protocol_engine/state/motion.py +373 -0
  475. opentrons/protocol_engine/state/pipettes.py +905 -0
  476. opentrons/protocol_engine/state/state.py +421 -0
  477. opentrons/protocol_engine/state/state_summary.py +36 -0
  478. opentrons/protocol_engine/state/tips.py +420 -0
  479. opentrons/protocol_engine/state/update_types.py +904 -0
  480. opentrons/protocol_engine/state/wells.py +290 -0
  481. opentrons/protocol_engine/types/__init__.py +308 -0
  482. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  483. opentrons/protocol_engine/types/command_annotations.py +53 -0
  484. opentrons/protocol_engine/types/deck_configuration.py +81 -0
  485. opentrons/protocol_engine/types/execution.py +96 -0
  486. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  487. opentrons/protocol_engine/types/instrument.py +47 -0
  488. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  489. opentrons/protocol_engine/types/labware.py +131 -0
  490. opentrons/protocol_engine/types/labware_movement.py +22 -0
  491. opentrons/protocol_engine/types/labware_offset_location.py +111 -0
  492. opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
  493. opentrons/protocol_engine/types/liquid.py +40 -0
  494. opentrons/protocol_engine/types/liquid_class.py +59 -0
  495. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  496. opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
  497. opentrons/protocol_engine/types/location.py +194 -0
  498. opentrons/protocol_engine/types/module.py +303 -0
  499. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  500. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  501. opentrons/protocol_engine/types/tip.py +18 -0
  502. opentrons/protocol_engine/types/util.py +21 -0
  503. opentrons/protocol_engine/types/well_position.py +124 -0
  504. opentrons/protocol_reader/__init__.py +37 -0
  505. opentrons/protocol_reader/extract_labware_definitions.py +66 -0
  506. opentrons/protocol_reader/file_format_validator.py +152 -0
  507. opentrons/protocol_reader/file_hasher.py +27 -0
  508. opentrons/protocol_reader/file_identifier.py +284 -0
  509. opentrons/protocol_reader/file_reader_writer.py +90 -0
  510. opentrons/protocol_reader/input_file.py +16 -0
  511. opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
  512. opentrons/protocol_reader/protocol_reader.py +188 -0
  513. opentrons/protocol_reader/protocol_source.py +124 -0
  514. opentrons/protocol_reader/role_analyzer.py +86 -0
  515. opentrons/protocol_runner/__init__.py +26 -0
  516. opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
  517. opentrons/protocol_runner/json_file_reader.py +55 -0
  518. opentrons/protocol_runner/json_translator.py +314 -0
  519. opentrons/protocol_runner/legacy_command_mapper.py +848 -0
  520. opentrons/protocol_runner/legacy_context_plugin.py +116 -0
  521. opentrons/protocol_runner/protocol_runner.py +530 -0
  522. opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
  523. opentrons/protocol_runner/run_orchestrator.py +496 -0
  524. opentrons/protocol_runner/task_queue.py +95 -0
  525. opentrons/protocols/__init__.py +6 -0
  526. opentrons/protocols/advanced_control/__init__.py +0 -0
  527. opentrons/protocols/advanced_control/common.py +38 -0
  528. opentrons/protocols/advanced_control/mix.py +60 -0
  529. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  530. opentrons/protocols/advanced_control/transfers/common.py +180 -0
  531. opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
  532. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
  533. opentrons/protocols/api_support/__init__.py +0 -0
  534. opentrons/protocols/api_support/constants.py +8 -0
  535. opentrons/protocols/api_support/deck_type.py +110 -0
  536. opentrons/protocols/api_support/definitions.py +18 -0
  537. opentrons/protocols/api_support/instrument.py +151 -0
  538. opentrons/protocols/api_support/labware_like.py +233 -0
  539. opentrons/protocols/api_support/tip_tracker.py +175 -0
  540. opentrons/protocols/api_support/types.py +32 -0
  541. opentrons/protocols/api_support/util.py +403 -0
  542. opentrons/protocols/bundle.py +89 -0
  543. opentrons/protocols/duration/__init__.py +4 -0
  544. opentrons/protocols/duration/errors.py +5 -0
  545. opentrons/protocols/duration/estimator.py +628 -0
  546. opentrons/protocols/execution/__init__.py +0 -0
  547. opentrons/protocols/execution/dev_types.py +181 -0
  548. opentrons/protocols/execution/errors.py +40 -0
  549. opentrons/protocols/execution/execute.py +84 -0
  550. opentrons/protocols/execution/execute_json_v3.py +275 -0
  551. opentrons/protocols/execution/execute_json_v4.py +359 -0
  552. opentrons/protocols/execution/execute_json_v5.py +28 -0
  553. opentrons/protocols/execution/execute_python.py +169 -0
  554. opentrons/protocols/execution/json_dispatchers.py +87 -0
  555. opentrons/protocols/execution/types.py +7 -0
  556. opentrons/protocols/geometry/__init__.py +0 -0
  557. opentrons/protocols/geometry/planning.py +297 -0
  558. opentrons/protocols/labware.py +312 -0
  559. opentrons/protocols/models/__init__.py +0 -0
  560. opentrons/protocols/models/json_protocol.py +679 -0
  561. opentrons/protocols/parameters/__init__.py +0 -0
  562. opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
  563. opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
  564. opentrons/protocols/parameters/exceptions.py +34 -0
  565. opentrons/protocols/parameters/parameter_definition.py +272 -0
  566. opentrons/protocols/parameters/types.py +17 -0
  567. opentrons/protocols/parameters/validation.py +267 -0
  568. opentrons/protocols/parse.py +671 -0
  569. opentrons/protocols/types.py +159 -0
  570. opentrons/py.typed +0 -0
  571. opentrons/resources/scripts/lpc21isp +0 -0
  572. opentrons/resources/smoothie-edge-8414642.hex +23010 -0
  573. opentrons/simulate.py +1065 -0
  574. opentrons/system/__init__.py +6 -0
  575. opentrons/system/camera.py +51 -0
  576. opentrons/system/log_control.py +59 -0
  577. opentrons/system/nmcli.py +856 -0
  578. opentrons/system/resin.py +24 -0
  579. opentrons/system/smoothie_update.py +15 -0
  580. opentrons/system/wifi.py +204 -0
  581. opentrons/tools/__init__.py +0 -0
  582. opentrons/tools/args_handler.py +22 -0
  583. opentrons/tools/write_pipette_memory.py +157 -0
  584. opentrons/types.py +618 -0
  585. opentrons/util/__init__.py +1 -0
  586. opentrons/util/async_helpers.py +166 -0
  587. opentrons/util/broker.py +84 -0
  588. opentrons/util/change_notifier.py +47 -0
  589. opentrons/util/entrypoint_util.py +278 -0
  590. opentrons/util/get_union_elements.py +26 -0
  591. opentrons/util/helpers.py +6 -0
  592. opentrons/util/linal.py +178 -0
  593. opentrons/util/logging_config.py +265 -0
  594. opentrons/util/logging_queue_handler.py +61 -0
  595. opentrons/util/performance_helpers.py +157 -0
  596. opentrons-8.6.0a1.dist-info/METADATA +37 -0
  597. opentrons-8.6.0a1.dist-info/RECORD +600 -0
  598. opentrons-8.6.0a1.dist-info/WHEEL +4 -0
  599. opentrons-8.6.0a1.dist-info/entry_points.txt +3 -0
  600. opentrons-8.6.0a1.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,990 @@
1
+ """Executor for liquid class based complex commands."""
2
+ from __future__ import annotations
3
+
4
+ import logging
5
+ from copy import deepcopy
6
+ from enum import Enum
7
+ from typing import TYPE_CHECKING, Optional, Union, Literal
8
+ from dataclasses import dataclass, field
9
+
10
+ from opentrons_shared_data.liquid_classes.liquid_class_definition import (
11
+ PositionReference,
12
+ Coordinate,
13
+ BlowoutLocation,
14
+ )
15
+
16
+ from opentrons.protocol_api._liquid_properties import (
17
+ Submerge,
18
+ TransferProperties,
19
+ MixProperties,
20
+ SingleDispenseProperties,
21
+ MultiDispenseProperties,
22
+ TouchTipProperties,
23
+ )
24
+ from opentrons.protocol_engine.errors import TouchTipDisabledError
25
+ from opentrons.types import Location, Point, Mount
26
+ from opentrons.protocols.advanced_control.transfers.transfer_liquid_utils import (
27
+ LocationCheckDescriptors,
28
+ check_current_volume_before_dispensing,
29
+ )
30
+ from opentrons.protocols.advanced_control.transfers import (
31
+ transfer_liquid_utils as tx_utils,
32
+ )
33
+
34
+ if TYPE_CHECKING:
35
+ from .well import WellCore
36
+ from .instrument import InstrumentCore
37
+ from ... import TrashBin, WasteChute
38
+
39
+ log = logging.getLogger(__name__)
40
+
41
+
42
+ AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP = 2
43
+
44
+
45
+ @dataclass
46
+ class LiquidAndAirGapPair:
47
+ """Pairing of a liquid and air gap in a tip, with air gap below the liquid in a tip."""
48
+
49
+ liquid: float = 0
50
+ air_gap: float = 0
51
+
52
+
53
+ @dataclass
54
+ class TipState:
55
+ """Carrier of the state of the pipette tip in use.
56
+
57
+ Properties:
58
+ last_liquid_and_air_gap_in_tip: The last liquid + air_gap combo in the tip.
59
+ This will only include the existing liquid and air gap in the tip that
60
+ an aspirate/ dispense interacts with. For example, the air gap from
61
+ a previous step that needs to be removed, or the liquid from a previous
62
+ aspirate that needs to be dispensed or the liquid that needs to be added to
63
+ during a consolidation.
64
+ ready_to_aspirate: Whether the pipette plunger is in a position that allows
65
+ correct aspiration. The starting state for the pipette at initialization of
66
+ `TransferComponentsExecutor`s should be ready_to_aspirate == True.
67
+ """
68
+
69
+ ready_to_aspirate: bool = True
70
+ # TODO: maybe use the tip contents from engine state instead.
71
+ last_liquid_and_air_gap_in_tip: LiquidAndAirGapPair = field(
72
+ default_factory=LiquidAndAirGapPair
73
+ )
74
+
75
+ def append_liquid(self, volume: float) -> None:
76
+ # Neither aspirate nor a dispense process should be adding liquid
77
+ # when there is an air gap present.
78
+ assert (
79
+ self.last_liquid_and_air_gap_in_tip.air_gap == 0
80
+ ), "Air gap present in the tip."
81
+ self.last_liquid_and_air_gap_in_tip.liquid += volume
82
+
83
+ def delete_liquid(self, volume: float) -> None:
84
+ # Neither aspirate nor a dispense process should be removing liquid
85
+ # when there is an air gap present.
86
+ assert (
87
+ self.last_liquid_and_air_gap_in_tip.air_gap == 0
88
+ ), "Air gap present in the tip."
89
+ self.last_liquid_and_air_gap_in_tip.liquid -= volume
90
+
91
+ def append_air_gap(self, volume: float) -> None:
92
+ # Neither aspirate nor a dispense process should be adding air gaps
93
+ # when there is already an air gap present.
94
+ assert (
95
+ self.last_liquid_and_air_gap_in_tip.air_gap == 0
96
+ ), "Air gap already present in the tip."
97
+ self.last_liquid_and_air_gap_in_tip.air_gap = volume
98
+
99
+ def delete_air_gap(self, volume: float) -> None:
100
+ assert (
101
+ self.last_liquid_and_air_gap_in_tip.air_gap == volume
102
+ ), "Last air gap volume doe not match the volume being removed"
103
+ self.last_liquid_and_air_gap_in_tip.air_gap = 0
104
+
105
+ def delete_last_air_gap_and_liquid(self) -> None:
106
+ air_gap_in_tip = self.last_liquid_and_air_gap_in_tip.air_gap
107
+ liquid_in_tip = self.last_liquid_and_air_gap_in_tip.liquid
108
+ if air_gap_in_tip:
109
+ self.delete_air_gap(air_gap_in_tip)
110
+ if liquid_in_tip:
111
+ self.delete_liquid(volume=liquid_in_tip)
112
+
113
+
114
+ class TransferType(Enum):
115
+ ONE_TO_ONE = "one_to_one"
116
+ MANY_TO_ONE = "many_to_one"
117
+ ONE_TO_MANY = "one_to_many"
118
+
119
+
120
+ class TransferComponentsExecutor:
121
+ def __init__(
122
+ self,
123
+ instrument_core: InstrumentCore,
124
+ transfer_properties: TransferProperties,
125
+ target_location: Union[Location, TrashBin, WasteChute],
126
+ target_well: Optional[WellCore],
127
+ tip_state: TipState,
128
+ transfer_type: TransferType,
129
+ ) -> None:
130
+ """Create a TransferComponentsExecutor instance.
131
+
132
+ One instance should be created to execute all the steps inside each of the
133
+ liquid class' transfer components- aspirate, dispense and multi-dispense.
134
+ The state of the TransferComponentsExecutor instance is expected to be valid
135
+ only for the component it was created.
136
+
137
+ For example, if we want to execute all the steps (submerge, dispense, retract, etc)
138
+ related to the 'dispense' component of a liquid-class based transfer, the class
139
+ will be used to initialize info about the dispense by assigning values
140
+ to class attributes as follows-
141
+ - target_location: the dispense location
142
+ - target_well: the well associated with dispense location, will be None when the
143
+ target_location argument is a TrashBin or WasteChute
144
+ - tip_state: the state of the tip before dispense component steps are executed
145
+ - transfer_type: whether the dispense component is being called as a part of a
146
+ 1-to-1 transfer or a consolidation or a distribution
147
+
148
+ These attributes will remain the same throughout the component's execution,
149
+ except `tip_state`, which will keep updating as fluids are handled.
150
+ """
151
+ self._instrument = instrument_core
152
+ self._transfer_properties = transfer_properties
153
+ self._target_location = target_location
154
+ self._target_well = target_well
155
+ self._tip_state: TipState = deepcopy(tip_state) # don't modify caller's object
156
+ self._transfer_type: TransferType = transfer_type
157
+
158
+ @property
159
+ def tip_state(self) -> TipState:
160
+ """Return the tip state."""
161
+ return self._tip_state
162
+
163
+ def submerge(
164
+ self,
165
+ submerge_properties: Submerge,
166
+ post_submerge_action: Literal["aspirate", "dispense"],
167
+ ) -> None:
168
+ """Execute submerge steps.
169
+
170
+ 1. move to position shown by positionReference + offset (should practically be a point outside/above the liquid).
171
+ Should raise an error if this point is inside the liquid?
172
+ For liquid meniscus this is easy to tell. Can’t be below meniscus
173
+ For reference pos of anything else, do not allow submerge position to be below aspirate position
174
+ 2. move to aspirate/dispense position at desired speed
175
+ 3. delay
176
+
177
+ If target location is a trash bin or waste chute, the pipette will move to the disposal location given,
178
+ remove air gap and delay
179
+ """
180
+ submerge_start_location: Union[Location, TrashBin, WasteChute]
181
+ if isinstance(self._target_location, Location):
182
+ assert self._target_well is not None
183
+ submerge_start_point = absolute_point_from_position_reference_and_offset(
184
+ well=self._target_well,
185
+ well_volume_difference=0,
186
+ position_reference=submerge_properties.start_position.position_reference,
187
+ offset=submerge_properties.start_position.offset,
188
+ mount=self._instrument.get_mount(),
189
+ )
190
+ submerge_start_location = Location(
191
+ point=submerge_start_point, labware=self._target_location.labware
192
+ )
193
+ tx_utils.raise_if_location_inside_liquid(
194
+ location=submerge_start_location,
195
+ well_core=self._target_well,
196
+ location_check_descriptors=LocationCheckDescriptors(
197
+ location_type="submerge start",
198
+ pipetting_action=post_submerge_action,
199
+ ),
200
+ logger=log,
201
+ )
202
+ else:
203
+ submerge_start_location = self._target_location
204
+
205
+ self._instrument.move_to(
206
+ location=submerge_start_location,
207
+ well_core=self._target_well,
208
+ force_direct=False,
209
+ minimum_z_height=None,
210
+ speed=None,
211
+ )
212
+ self._remove_air_gap(location=submerge_start_location)
213
+ if isinstance(self._target_location, Location):
214
+ self._instrument.move_to(
215
+ location=self._target_location,
216
+ well_core=self._target_well,
217
+ force_direct=True,
218
+ minimum_z_height=None,
219
+ speed=submerge_properties.speed,
220
+ )
221
+
222
+ if submerge_properties.delay.enabled and submerge_properties.delay.duration:
223
+ self._instrument.delay(submerge_properties.delay.duration)
224
+
225
+ def aspirate_and_wait(self, volume: float) -> None:
226
+ """Aspirate according to aspirate properties and wait if enabled."""
227
+ # TODO: handle volume correction
228
+ assert (
229
+ isinstance(self._target_location, Location)
230
+ and self._target_well is not None
231
+ )
232
+ aspirate_props = self._transfer_properties.aspirate
233
+ correction_volume = aspirate_props.correction_by_volume.get_for_volume(
234
+ self._instrument.get_current_volume() + volume
235
+ )
236
+ self._instrument.aspirate(
237
+ location=self._target_location,
238
+ well_core=None,
239
+ volume=volume,
240
+ rate=1,
241
+ flow_rate=aspirate_props.flow_rate_by_volume.get_for_volume(volume),
242
+ in_place=True,
243
+ correction_volume=correction_volume,
244
+ )
245
+ self._tip_state.append_liquid(volume)
246
+ delay_props = aspirate_props.delay
247
+ if delay_props.enabled and delay_props.duration:
248
+ self._instrument.delay(delay_props.duration)
249
+
250
+ def dispense_and_wait(
251
+ self,
252
+ dispense_properties: Union[SingleDispenseProperties, MultiDispenseProperties],
253
+ volume: float,
254
+ push_out_override: Optional[float],
255
+ ) -> None:
256
+ """Dispense according to dispense properties and wait if enabled."""
257
+ current_vol = self._instrument.get_current_volume()
258
+ check_current_volume_before_dispensing(
259
+ current_volume=current_vol, dispense_volume=volume
260
+ )
261
+ correction_volume = dispense_properties.correction_by_volume.get_for_volume(
262
+ current_vol - volume
263
+ )
264
+ self._instrument.dispense(
265
+ location=self._target_location,
266
+ well_core=None,
267
+ volume=volume,
268
+ rate=1,
269
+ flow_rate=dispense_properties.flow_rate_by_volume.get_for_volume(volume),
270
+ in_place=True,
271
+ push_out=push_out_override,
272
+ correction_volume=correction_volume,
273
+ )
274
+ if push_out_override:
275
+ # If a push out was performed, we need to reset the plunger before we can aspirate again
276
+ self._tip_state.ready_to_aspirate = False
277
+ self._tip_state.delete_liquid(volume)
278
+ dispense_delay = dispense_properties.delay
279
+ if dispense_delay.enabled and dispense_delay.duration:
280
+ self._instrument.delay(dispense_delay.duration)
281
+
282
+ def mix(self, mix_properties: MixProperties, last_dispense_push_out: bool) -> None:
283
+ """Execute mix steps.
284
+
285
+ 1. Use same flow rates and delays as aspirate and dispense
286
+ 2. Do [(aspirate + dispense) x repetitions] at the same position
287
+ 3. Do NOT push out at the end of dispense
288
+ 4. USE the delay property from aspirate & dispense during mix as well (flow rate and delay are coordinated with each other)
289
+ 5. Do not mix during consolidation
290
+ NOTE: For most of our built-in definitions, we will keep _mix_ off because it is a very application specific thing.
291
+ We should mention in our docs that users should adjust this property according to their application.
292
+ """
293
+ if not mix_properties.enabled or not isinstance(
294
+ self._target_location, Location
295
+ ):
296
+ return
297
+ # Assertion only for mypy purposes
298
+ assert (
299
+ mix_properties.repetitions is not None
300
+ and mix_properties.volume is not None
301
+ and self._target_well is not None
302
+ )
303
+ push_out_vol = (
304
+ self._transfer_properties.dispense.push_out_by_volume.get_for_volume(
305
+ mix_properties.volume
306
+ )
307
+ )
308
+ for n in range(mix_properties.repetitions, 0, -1):
309
+ self.aspirate_and_wait(volume=mix_properties.volume)
310
+ self.dispense_and_wait(
311
+ dispense_properties=self._transfer_properties.dispense, # TODO: check that using single-dispense props during mix is correct
312
+ volume=mix_properties.volume,
313
+ push_out_override=push_out_vol
314
+ if last_dispense_push_out is True and n == 1
315
+ else 0,
316
+ )
317
+
318
+ def pre_wet(
319
+ self,
320
+ volume: float,
321
+ ) -> None:
322
+ """Do a pre-wet.
323
+
324
+ - 1 combo of aspirate + dispense at the same flow rate as specified in asp & disp and the delays in asp & disp
325
+ - Use the target volume/ volume we will be aspirating
326
+ - No push out
327
+ - No pre-wet for consolidation
328
+ """
329
+ if not self._transfer_properties.aspirate.pre_wet:
330
+ return
331
+ mix_props = MixProperties(_enabled=True, _repetitions=1, _volume=volume)
332
+ self.mix(mix_properties=mix_props, last_dispense_push_out=False)
333
+
334
+ def retract_after_aspiration(
335
+ self, volume: float, add_air_gap: Optional[bool] = True
336
+ ) -> None:
337
+ """Execute post-aspiration retraction steps.
338
+
339
+ 1. Move TO the position reference+offset AT the specified speed
340
+ Raise error if retract is below aspirate position or below the meniscus
341
+ 2. Delay
342
+ 3. Touch tip
343
+ - Move to the Z offset position
344
+ - Touch tip to the sides at the specified speed (tip moves back to the center as part of touch tip)
345
+ - Return back to the retract position
346
+ 4. Air gap
347
+ - If the retract location is at or above the safe location of
348
+ AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP, then add the air gap at the
349
+ retract location (where the pipette is already assumed to be).
350
+ - If the retract location is below the safe location, then move to
351
+ the safe location and then add the air gap.
352
+ - Air gap volume depends on the amount of liquid in the pipette.
353
+ So, if the total aspirated volume is 20, use the value for airGapByVolume[20]
354
+ Flow rate = max(aspirateFlowRate, (airGapByVolume)/sec)
355
+ - Use post-aspirate delay
356
+
357
+ Args:
358
+ volume: dispense volume
359
+ add_air_gap: whether to add an air gap before moving away from the current well.
360
+ This value is True for all retractions, except when retracting
361
+ during a multi-dispense. Value of add_air_gap during multi-dispense
362
+ will depend on whether a conditioning volume is used.
363
+ """
364
+ assert (
365
+ isinstance(self._target_location, Location)
366
+ and self._target_well is not None
367
+ )
368
+ retract_props = self._transfer_properties.aspirate.retract
369
+ retract_point = absolute_point_from_position_reference_and_offset(
370
+ well=self._target_well,
371
+ well_volume_difference=0,
372
+ position_reference=retract_props.end_position.position_reference,
373
+ offset=retract_props.end_position.offset,
374
+ mount=self._instrument.get_mount(),
375
+ )
376
+ retract_location = Location(
377
+ retract_point, labware=self._target_location.labware
378
+ )
379
+ tx_utils.raise_if_location_inside_liquid(
380
+ location=retract_location,
381
+ well_core=self._target_well,
382
+ location_check_descriptors=LocationCheckDescriptors(
383
+ location_type="retract end",
384
+ pipetting_action="aspirate",
385
+ ),
386
+ logger=log,
387
+ )
388
+ self._instrument.move_to(
389
+ location=retract_location,
390
+ well_core=self._target_well,
391
+ force_direct=True,
392
+ minimum_z_height=None,
393
+ speed=retract_props.speed,
394
+ )
395
+ retract_delay = retract_props.delay
396
+ if retract_delay.enabled and retract_delay.duration:
397
+ self._instrument.delay(retract_delay.duration)
398
+ touch_tip_props = retract_props.touch_tip
399
+ if touch_tip_props.enabled:
400
+ assert (
401
+ touch_tip_props.speed is not None
402
+ and touch_tip_props.z_offset is not None
403
+ and touch_tip_props.mm_from_edge is not None
404
+ )
405
+ self._instrument.touch_tip(
406
+ location=retract_location,
407
+ well_core=self._target_well,
408
+ radius=1,
409
+ z_offset=touch_tip_props.z_offset,
410
+ speed=touch_tip_props.speed,
411
+ mm_from_edge=touch_tip_props.mm_from_edge,
412
+ )
413
+ self._instrument.move_to(
414
+ location=retract_location,
415
+ well_core=self._target_well,
416
+ force_direct=True,
417
+ minimum_z_height=None,
418
+ # Full speed because the tip will already be out of the liquid
419
+ speed=None,
420
+ )
421
+ # For consolidate, we need to know the total amount that is in the pipette
422
+ # since this may not be the first aspirate
423
+ if self._transfer_type == TransferType.MANY_TO_ONE:
424
+ volume_for_air_gap = self._instrument.get_current_volume()
425
+ else:
426
+ volume_for_air_gap = volume
427
+ if add_air_gap:
428
+ # If we need to add air gap, move to a safe location above the well if
429
+ # the retract location is not already at or above this safe location
430
+ if (
431
+ retract_location.point.z
432
+ < self._target_well.get_top(AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP).z
433
+ ):
434
+ self._instrument.move_to(
435
+ location=Location(
436
+ point=Point(
437
+ retract_location.point.x,
438
+ retract_location.point.y,
439
+ self._target_well.get_top(
440
+ AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP
441
+ ).z,
442
+ ),
443
+ labware=retract_location.labware,
444
+ ),
445
+ well_core=self._target_well,
446
+ force_direct=True,
447
+ minimum_z_height=None,
448
+ # Full speed because the tip will already be out of the liquid
449
+ speed=None,
450
+ )
451
+ self._add_air_gap(
452
+ air_gap_volume=self._transfer_properties.aspirate.retract.air_gap_by_volume.get_for_volume(
453
+ volume_for_air_gap
454
+ )
455
+ )
456
+
457
+ def retract_after_dispensing(
458
+ self,
459
+ trash_location: Union[Location, TrashBin, WasteChute],
460
+ source_location: Optional[Location],
461
+ source_well: Optional[WellCore],
462
+ add_final_air_gap: bool,
463
+ ) -> None:
464
+ """Execute post-dispense retraction steps.
465
+ 1. Position ref+offset is the ending position. Move to this position using specified speed
466
+ 2. If blowout is enabled and “destination”
467
+ - Do blow-out (at the retract position)
468
+ - Leave plunger down
469
+ 3. Touch-tip
470
+ 4. If not ready-to-aspirate
471
+ - Prepare-to-aspirate (at the retract position)
472
+ 5. Air-gap (at the retract position)
473
+ - This air gap is for preventing any stray droplets from falling while moving the pipette.
474
+ It will be performed out of caution even if we just did a blow_out and should *hypothetically*
475
+ have no liquid left in the tip.
476
+ - This air gap will be removed at the next aspirate.
477
+ If this is the last step of the transfer, and we aren't dropping the tip off,
478
+ then the air gap will be left as is(?).
479
+ 6. If blowout is “source” or “trash”
480
+ - Move to location (top of Well)
481
+ - Do blow-out (top of well)
482
+ - Do touch-tip (?????) (only if it’s in a non-trash location)
483
+ - Prepare-to-aspirate (top of well)
484
+ - Do air-gap (top of well)
485
+ 7. If drop tip, move to drop tip location, drop tip
486
+
487
+ If target location is a trash bin or waste chute, the retract movement step is skipped along with touch tip,
488
+ even if it is enabled.
489
+ """
490
+ retract_props = self._transfer_properties.dispense.retract
491
+
492
+ retract_location: Union[Location, TrashBin, WasteChute]
493
+ if isinstance(self._target_location, Location):
494
+ assert self._target_well is not None
495
+ retract_point = absolute_point_from_position_reference_and_offset(
496
+ well=self._target_well,
497
+ well_volume_difference=0,
498
+ position_reference=retract_props.end_position.position_reference,
499
+ offset=retract_props.end_position.offset,
500
+ mount=self._instrument.get_mount(),
501
+ )
502
+ retract_location = Location(
503
+ retract_point, labware=self._target_location.labware
504
+ )
505
+ tx_utils.raise_if_location_inside_liquid(
506
+ location=retract_location,
507
+ well_core=self._target_well,
508
+ location_check_descriptors=LocationCheckDescriptors(
509
+ location_type="retract end",
510
+ pipetting_action="dispense",
511
+ ),
512
+ logger=log,
513
+ )
514
+ self._instrument.move_to(
515
+ location=retract_location,
516
+ well_core=self._target_well,
517
+ force_direct=True,
518
+ minimum_z_height=None,
519
+ speed=retract_props.speed,
520
+ )
521
+ else:
522
+ retract_location = self._target_location
523
+
524
+ # TODO should we delay here for a trash despite not having a "retract"?
525
+ retract_delay = retract_props.delay
526
+ if retract_delay.enabled and retract_delay.duration:
527
+ self._instrument.delay(retract_delay.duration)
528
+
529
+ blowout_props = retract_props.blowout
530
+ if (
531
+ blowout_props.enabled
532
+ and blowout_props.location == BlowoutLocation.DESTINATION
533
+ ):
534
+ assert blowout_props.flow_rate is not None
535
+ self._instrument.set_flow_rate(blow_out=blowout_props.flow_rate)
536
+ self._instrument.blow_out(
537
+ location=retract_location,
538
+ well_core=None,
539
+ in_place=True,
540
+ )
541
+ self._tip_state.ready_to_aspirate = False
542
+ is_final_air_gap = (
543
+ blowout_props.enabled
544
+ and blowout_props.location == BlowoutLocation.DESTINATION
545
+ ) or not blowout_props.enabled
546
+
547
+ if is_final_air_gap and not add_final_air_gap:
548
+ air_gap_volume = 0.0
549
+ else:
550
+ air_gap_volume = retract_props.air_gap_by_volume.get_for_volume(0)
551
+ # Regardless of the blowout location, do touch tip and air gap
552
+ # when leaving the dispense well. If this will be the final air gap, i.e,
553
+ # we won't be moving to a Trash or a Source for Blowout after this air gap,
554
+ # then skip the final air gap if we have been told to do so.
555
+ self._do_touch_tip_and_air_gap_after_dispense(
556
+ touch_tip_properties=retract_props.touch_tip,
557
+ location=retract_location,
558
+ well=self._target_well,
559
+ air_gap_volume=air_gap_volume,
560
+ )
561
+
562
+ if (
563
+ blowout_props.enabled
564
+ and blowout_props.location != BlowoutLocation.DESTINATION
565
+ ):
566
+ # TODO: no-op touch tip if touch tip is enabled and blowout is in trash/ reservoir/ any labware with touch-tip disabled
567
+ assert blowout_props.flow_rate is not None
568
+ self._instrument.set_flow_rate(blow_out=blowout_props.flow_rate)
569
+ touch_tip_and_air_gap_location: Union[Location, TrashBin, WasteChute]
570
+ if blowout_props.location == BlowoutLocation.SOURCE:
571
+ if source_location is None or source_well is None:
572
+ raise RuntimeError(
573
+ "Blowout location is 'source' but source location &/or well is not provided."
574
+ )
575
+ # TODO: check if we should add a blowout location z-offset in liq class definition
576
+ self._instrument.blow_out(
577
+ location=Location(
578
+ source_well.get_top(0), labware=source_location.labware
579
+ ),
580
+ well_core=source_well,
581
+ in_place=False,
582
+ )
583
+ touch_tip_and_air_gap_location = Location(
584
+ source_well.get_top(0), labware=source_location.labware
585
+ )
586
+ touch_tip_and_air_gap_well = source_well
587
+ else:
588
+ self._instrument.blow_out(
589
+ location=trash_location,
590
+ well_core=None,
591
+ in_place=False,
592
+ )
593
+ touch_tip_and_air_gap_location = trash_location
594
+ touch_tip_and_air_gap_well = (
595
+ # We have already established that trash location of `Location` type
596
+ # has its `labware` as `Well` type.
597
+ trash_location.labware.as_well()._core # type: ignore[assignment]
598
+ if isinstance(trash_location, Location)
599
+ else None
600
+ )
601
+ # A non-multi-dispense blowout will only have air and maybe droplets in the tip
602
+ # since we only blowout after dispensing the full tip contents.
603
+ # So delete the air gap from tip state
604
+ last_air_gap = self._tip_state.last_liquid_and_air_gap_in_tip.air_gap
605
+ self._tip_state.delete_air_gap(last_air_gap)
606
+ self._tip_state.ready_to_aspirate = False
607
+
608
+ air_gap_volume = (
609
+ retract_props.air_gap_by_volume.get_for_volume(0)
610
+ if add_final_air_gap
611
+ else 0.0
612
+ )
613
+ # Do touch tip and air gap again after blowing out into source well or trash
614
+ self._do_touch_tip_and_air_gap_after_dispense(
615
+ touch_tip_properties=retract_props.touch_tip,
616
+ location=touch_tip_and_air_gap_location,
617
+ well=touch_tip_and_air_gap_well,
618
+ air_gap_volume=air_gap_volume,
619
+ )
620
+
621
+ def retract_during_multi_dispensing( # noqa: C901
622
+ self,
623
+ trash_location: Union[Location, TrashBin, WasteChute],
624
+ source_location: Optional[Location],
625
+ source_well: Optional[WellCore],
626
+ conditioning_volume: float,
627
+ add_final_air_gap: bool,
628
+ is_last_retract: bool,
629
+ ) -> None:
630
+ """Execute post-dispense retraction steps when the dispense is a part of a multi-dispense.
631
+
632
+ Args:
633
+ trash_location: Location where we can drop tips or blowout, if set to do so
634
+ source_location: Location where we can blowout, if set to do so
635
+ source_well: Well where we can blowout, if set to do so
636
+ conditioning_volume: Conditioning volume used for this multi-dispense. Can be 0
637
+ add_final_air_gap: Whether we should add the final air gap of the step
638
+ is_last_retract: Whether this is the last retract of the multi-dispense steps,
639
+ i.e., this is part of the last dispense in the series of consecutive dispenses.
640
+ This dispense might not be the last dispense of the entire distribution.
641
+
642
+ This function is mostly similar to the single-dispense retract function except
643
+ that it handles air gaps differently based on the disposal volume, conditioning volume
644
+ and whether we are moving to another dispense or going back to the source.
645
+ """
646
+ assert (
647
+ isinstance(self._target_location, Location)
648
+ and self._target_well is not None
649
+ )
650
+ assert self._transfer_properties.multi_dispense is not None
651
+
652
+ retract_props = self._transfer_properties.multi_dispense.retract
653
+ retract_point = absolute_point_from_position_reference_and_offset(
654
+ well=self._target_well,
655
+ well_volume_difference=0,
656
+ position_reference=retract_props.end_position.position_reference,
657
+ offset=retract_props.end_position.offset,
658
+ mount=self._instrument.get_mount(),
659
+ )
660
+ retract_location = Location(
661
+ retract_point, labware=self._target_location.labware
662
+ )
663
+ tx_utils.raise_if_location_inside_liquid(
664
+ location=retract_location,
665
+ well_core=self._target_well,
666
+ location_check_descriptors=LocationCheckDescriptors(
667
+ location_type="retract end",
668
+ pipetting_action="dispense",
669
+ ),
670
+ logger=log,
671
+ )
672
+ self._instrument.move_to(
673
+ location=retract_location,
674
+ well_core=self._target_well,
675
+ force_direct=True,
676
+ minimum_z_height=None,
677
+ speed=retract_props.speed,
678
+ )
679
+ retract_delay = retract_props.delay
680
+ if retract_delay.enabled and retract_delay.duration:
681
+ self._instrument.delay(retract_delay.duration)
682
+
683
+ blowout_props = retract_props.blowout
684
+ if (
685
+ is_last_retract
686
+ and blowout_props.enabled
687
+ and blowout_props.location == BlowoutLocation.DESTINATION
688
+ ):
689
+ assert blowout_props.flow_rate is not None
690
+ self._instrument.set_flow_rate(blow_out=blowout_props.flow_rate)
691
+ self._instrument.blow_out(
692
+ location=retract_location,
693
+ well_core=None,
694
+ in_place=True,
695
+ )
696
+ # A blowout will remove all air gap and liquid (disposal volume) from the tip
697
+ # so delete them from tip state (although practically, there will not be
698
+ # any air gaps in the tip before blowing out in the destination well)
699
+ self._tip_state.delete_last_air_gap_and_liquid()
700
+ self._tip_state.ready_to_aspirate = False
701
+
702
+ # A retract will perform total of two air gaps if we need to blow out in source or trash:
703
+ # - 1st air gap: added before leaving the destination volume to go to src/ trash
704
+ # - 2nd air gap: added before leaving the blowout location to go to src or tip drop location
705
+ # But if blowout is disabled or is set to Destination well, then only one air gap
706
+ # will be added after retracting, before moving to src or tip drop location.
707
+ # `is_final_air_gap_of_current_retract` tells us whether the next air gap
708
+ # we will be adding, is going to be the last air gap of this step.
709
+ is_final_air_gap_of_current_retract = (
710
+ blowout_props.enabled
711
+ and blowout_props.location == BlowoutLocation.DESTINATION
712
+ ) or not blowout_props.enabled
713
+
714
+ # Whether we should add the next air gap depends on the cases as shown below.
715
+ # The main points when deciding this-
716
+ # - When we have used a conditioning volume, we do not want to add air gaps
717
+ # while there's still liquid in tip for dispensing
718
+ # - If we are not using conditioning volume then we want to add gaps just like
719
+ # we do during the one-to-one transfers
720
+ # - If this will be the last air gap of the step, if the above two conditions
721
+ # indicate that we should be adding an air gap, use `add_final_air_gap` as
722
+ # the final decider of whether to add the air gap.
723
+ if is_final_air_gap_of_current_retract:
724
+ if conditioning_volume > 0:
725
+ add_air_gap = is_last_retract and add_final_air_gap
726
+ else:
727
+ add_air_gap = add_final_air_gap
728
+ else:
729
+ if conditioning_volume > 0:
730
+ add_air_gap = is_last_retract
731
+ else:
732
+ add_air_gap = True
733
+
734
+ air_gap_volume = (
735
+ retract_props.air_gap_by_volume.get_for_volume(
736
+ self.tip_state.last_liquid_and_air_gap_in_tip.liquid
737
+ )
738
+ if add_air_gap
739
+ else 0.0
740
+ )
741
+
742
+ # Regardless of the blowout location, do touch tip
743
+ # when leaving the dispense well.
744
+ # Add an air gap depending on conditioning volume + whether this is
745
+ # the last step of a multi-dispense sequence + whether this is the last step
746
+ # of the entire liquid distribution.
747
+ self._do_touch_tip_and_air_gap_after_dispense(
748
+ touch_tip_properties=retract_props.touch_tip,
749
+ location=retract_location,
750
+ well=self._target_well,
751
+ air_gap_volume=air_gap_volume,
752
+ )
753
+
754
+ if (
755
+ is_last_retract # We can do a blowout only on the last multi-dispense step
756
+ and blowout_props.enabled
757
+ and blowout_props.location != BlowoutLocation.DESTINATION
758
+ ):
759
+ assert blowout_props.flow_rate is not None
760
+ self._instrument.set_flow_rate(blow_out=blowout_props.flow_rate)
761
+ touch_tip_and_air_gap_location: Union[Location, TrashBin, WasteChute]
762
+ if blowout_props.location == BlowoutLocation.SOURCE:
763
+ if source_location is None or source_well is None:
764
+ raise RuntimeError(
765
+ "Blowout location is 'source' but source location &/or well is not provided."
766
+ )
767
+ # TODO: check if we should add a blowout location z-offset in liq class definition
768
+ self._instrument.blow_out(
769
+ location=Location(
770
+ source_well.get_top(0), labware=source_location.labware
771
+ ),
772
+ well_core=source_well,
773
+ in_place=False,
774
+ )
775
+ touch_tip_and_air_gap_location = Location(
776
+ source_well.get_top(0), labware=source_location.labware
777
+ )
778
+ touch_tip_and_air_gap_well = source_well
779
+ else:
780
+ self._instrument.blow_out(
781
+ location=trash_location,
782
+ well_core=None,
783
+ in_place=False,
784
+ )
785
+ touch_tip_and_air_gap_location = trash_location
786
+ touch_tip_and_air_gap_well = (
787
+ # We have already established that trash location of `Location` type
788
+ # has its `labware` as `Well` type.
789
+ trash_location.labware.as_well()._core # type: ignore[assignment]
790
+ if isinstance(trash_location, Location)
791
+ else None
792
+ )
793
+ # A blowout will remove all air gap and liquid (disposal volume) from the tip
794
+ # so delete them from tip state
795
+ self._tip_state.delete_last_air_gap_and_liquid()
796
+ self._tip_state.ready_to_aspirate = False
797
+
798
+ if (
799
+ # Same check as before for when it's the final air gap of current retract
800
+ conditioning_volume > 0
801
+ and is_last_retract
802
+ and add_final_air_gap
803
+ ):
804
+ # The volume in tip at this point should be 0uL
805
+ air_gap_volume = retract_props.air_gap_by_volume.get_for_volume(0)
806
+ else:
807
+ air_gap_volume = 0
808
+ # Do touch tip and air gap again after blowing out into source well or trash
809
+ self._do_touch_tip_and_air_gap_after_dispense(
810
+ touch_tip_properties=retract_props.touch_tip,
811
+ location=touch_tip_and_air_gap_location,
812
+ well=touch_tip_and_air_gap_well,
813
+ air_gap_volume=air_gap_volume,
814
+ )
815
+
816
+ def _do_touch_tip_and_air_gap_after_dispense( # noqa: C901
817
+ self,
818
+ touch_tip_properties: TouchTipProperties,
819
+ location: Union[Location, TrashBin, WasteChute],
820
+ well: Optional[WellCore],
821
+ air_gap_volume: float,
822
+ ) -> None:
823
+ """Perform touch tip and air gap as part of post-dispense retract.
824
+
825
+ If the retract location is at or above the safe location of
826
+ AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP, then add the air gap at the retract location
827
+ (where the pipette is already assumed to be at).
828
+
829
+ If the retract location is below the safe location, then move to the safe location
830
+ and then add the air gap.
831
+
832
+ Note: if the plunger needs to be adjusted to prepare for aspirate, it will be done
833
+ at the same location where the air gap will be added.
834
+ """
835
+ if touch_tip_properties.enabled:
836
+ assert (
837
+ touch_tip_properties.speed is not None
838
+ and touch_tip_properties.z_offset is not None
839
+ and touch_tip_properties.mm_from_edge is not None
840
+ )
841
+ # TODO:, check that when blow out is a non-dest-well,
842
+ # whether the touch tip params from transfer props should be used for
843
+ # both dest-well touch tip and non-dest-well touch tip.
844
+ if isinstance(location, Location) and well is not None:
845
+ try:
846
+ self._instrument.touch_tip(
847
+ location=location,
848
+ well_core=well,
849
+ radius=1,
850
+ z_offset=touch_tip_properties.z_offset,
851
+ speed=touch_tip_properties.speed,
852
+ mm_from_edge=touch_tip_properties.mm_from_edge,
853
+ )
854
+ except TouchTipDisabledError:
855
+ # TODO: log a warning
856
+ pass
857
+
858
+ # Move back to the 'retract' position
859
+ self._instrument.move_to(
860
+ location=location,
861
+ well_core=well,
862
+ force_direct=True,
863
+ minimum_z_height=None,
864
+ # Full speed because the tip will already be out of the liquid
865
+ speed=None,
866
+ )
867
+ if air_gap_volume > 0 or not self._tip_state.ready_to_aspirate:
868
+ # If we need to move the plunger up either to prepare for aspirate or to add air gap,
869
+ # move to a safe location above the well if the retract location is not already
870
+ # at or above this safe location
871
+ if isinstance(location, Location):
872
+ assert well is not None # For mypy purposes only
873
+ if (
874
+ location.point.z
875
+ < well.get_top(AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP).z
876
+ ):
877
+ self._instrument.move_to(
878
+ location=Location(
879
+ point=Point(
880
+ location.point.x,
881
+ location.point.y,
882
+ well.get_top(AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP).z,
883
+ ),
884
+ labware=location.labware,
885
+ ),
886
+ well_core=well,
887
+ force_direct=True,
888
+ minimum_z_height=None,
889
+ speed=None,
890
+ )
891
+ else:
892
+ if (
893
+ location.offset.z
894
+ < location.top(
895
+ x=0, y=0, z=AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP
896
+ ).offset.z
897
+ ):
898
+ self._instrument.move_to(
899
+ location=location.top(
900
+ x=location.offset.x,
901
+ y=location.offset.y,
902
+ z=AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP,
903
+ ),
904
+ well_core=None,
905
+ force_direct=True,
906
+ minimum_z_height=None,
907
+ speed=None,
908
+ )
909
+
910
+ if not self._tip_state.ready_to_aspirate:
911
+ self._instrument.prepare_to_aspirate()
912
+ self._tip_state.ready_to_aspirate = True
913
+ if air_gap_volume > 0:
914
+ self._add_air_gap(air_gap_volume=air_gap_volume)
915
+
916
+ def _add_air_gap(
917
+ self,
918
+ air_gap_volume: float,
919
+ ) -> None:
920
+ """Add an air gap."""
921
+ if air_gap_volume == 0:
922
+ return
923
+ aspirate_props = self._transfer_properties.aspirate
924
+ correction_volume = aspirate_props.correction_by_volume.get_for_volume(
925
+ self._instrument.get_current_volume() + air_gap_volume
926
+ )
927
+ # The minimum flow rate should be air_gap_volume per second
928
+ flow_rate = max(
929
+ aspirate_props.flow_rate_by_volume.get_for_volume(air_gap_volume),
930
+ air_gap_volume,
931
+ )
932
+ self._instrument.air_gap_in_place(
933
+ volume=air_gap_volume,
934
+ flow_rate=flow_rate,
935
+ correction_volume=correction_volume,
936
+ )
937
+ delay_props = aspirate_props.delay
938
+ if delay_props.enabled and delay_props.duration:
939
+ self._instrument.delay(delay_props.duration)
940
+ self._tip_state.append_air_gap(air_gap_volume)
941
+
942
+ def _remove_air_gap(self, location: Union[Location, TrashBin, WasteChute]) -> None:
943
+ """Remove a previously added air gap."""
944
+ last_air_gap = self._tip_state.last_liquid_and_air_gap_in_tip.air_gap
945
+ dispense_props = self._transfer_properties.dispense
946
+ self._instrument.remove_air_gap_during_transfer_with_liquid_class(
947
+ last_air_gap=last_air_gap,
948
+ dispense_props=dispense_props,
949
+ location=location,
950
+ )
951
+ self._tip_state.delete_air_gap(last_air_gap)
952
+
953
+
954
+ def absolute_point_from_position_reference_and_offset(
955
+ well: WellCore,
956
+ well_volume_difference: float,
957
+ position_reference: PositionReference,
958
+ offset: Coordinate,
959
+ mount: Mount,
960
+ ) -> Point:
961
+ """Return the absolute point, given the well, the position reference and offset.
962
+
963
+ If using meniscus as the position reference, well_volume_difference should be specified.
964
+ `well_volume_difference` is the expected *difference* in well volume we want to consider
965
+ when estimating the height of the liquid meniscus after an aspirate/ dispense.
966
+ So, for liquid height estimation after an aspirate, well_volume_difference is
967
+ expected to be a -ve value while for a dispense, it will be a +ve value.
968
+ """
969
+ match position_reference:
970
+ case PositionReference.WELL_TOP:
971
+ reference_point = well.get_top(0)
972
+ case PositionReference.WELL_BOTTOM:
973
+ reference_point = well.get_bottom(0)
974
+ case PositionReference.WELL_CENTER:
975
+ reference_point = well.get_center()
976
+ case PositionReference.LIQUID_MENISCUS:
977
+ estimated_liquid_height = well.estimate_liquid_height_after_pipetting(
978
+ mount=mount,
979
+ operation_volume=well_volume_difference,
980
+ )
981
+ if isinstance(estimated_liquid_height, (float, int)):
982
+ reference_point = well.get_bottom(z_offset=estimated_liquid_height)
983
+ else:
984
+ # If estimated liquid height gives a SimulatedProbeResult then
985
+ # assume meniscus is at well center.
986
+ # Will this cause more harm than good? Is there a better alternative to this?
987
+ reference_point = well.get_center()
988
+ case _:
989
+ raise ValueError(f"Unknown position reference {position_reference}")
990
+ return reference_point + Point(offset.x, offset.y, offset.z)