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,1006 @@
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, replace
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.types import Location, Point, Mount
25
+ from opentrons.protocols.advanced_control.transfers.transfer_liquid_utils import (
26
+ LocationCheckDescriptors,
27
+ check_current_volume_before_dispensing,
28
+ )
29
+ from opentrons.protocols.advanced_control.transfers import (
30
+ transfer_liquid_utils as tx_utils,
31
+ )
32
+
33
+ if TYPE_CHECKING:
34
+ from .well import WellCore
35
+ from .instrument import InstrumentCore
36
+ from ... import TrashBin, WasteChute
37
+
38
+ log = logging.getLogger(__name__)
39
+
40
+
41
+ AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP = 2
42
+
43
+
44
+ @dataclass
45
+ class LiquidAndAirGapPair:
46
+ """Pairing of a liquid and air gap in a tip, with air gap below the liquid in a tip."""
47
+
48
+ liquid: float = 0
49
+ air_gap: float = 0
50
+
51
+
52
+ @dataclass
53
+ class TipState:
54
+ """Carrier of the state of the pipette tip in use.
55
+
56
+ Properties:
57
+ last_liquid_and_air_gap_in_tip: The last liquid + air_gap combo in the tip.
58
+ This will only include the existing liquid and air gap in the tip that
59
+ an aspirate/ dispense interacts with. For example, the air gap from
60
+ a previous step that needs to be removed, or the liquid from a previous
61
+ aspirate that needs to be dispensed or the liquid that needs to be added to
62
+ during a consolidation.
63
+ ready_to_aspirate: Whether the pipette plunger is in a position that allows
64
+ correct aspiration. The starting state for the pipette at initialization of
65
+ `TransferComponentsExecutor`s should be ready_to_aspirate == True.
66
+ """
67
+
68
+ ready_to_aspirate: bool = True
69
+ # TODO: maybe use the tip contents from engine state instead.
70
+ last_liquid_and_air_gap_in_tip: LiquidAndAirGapPair = field(
71
+ default_factory=LiquidAndAirGapPair
72
+ )
73
+
74
+ def append_liquid(self, volume: float) -> None:
75
+ # Neither aspirate nor a dispense process should be adding liquid
76
+ # when there is an air gap present.
77
+ assert (
78
+ self.last_liquid_and_air_gap_in_tip.air_gap == 0
79
+ ), "Air gap present in the tip."
80
+ self.last_liquid_and_air_gap_in_tip.liquid += volume
81
+
82
+ def delete_liquid(self, volume: float) -> None:
83
+ # Neither aspirate nor a dispense process should be removing liquid
84
+ # when there is an air gap present.
85
+ assert (
86
+ self.last_liquid_and_air_gap_in_tip.air_gap == 0
87
+ ), "Air gap present in the tip."
88
+ self.last_liquid_and_air_gap_in_tip.liquid -= volume
89
+
90
+ def append_air_gap(self, volume: float) -> None:
91
+ # Neither aspirate nor a dispense process should be adding air gaps
92
+ # when there is already an air gap present.
93
+ assert (
94
+ self.last_liquid_and_air_gap_in_tip.air_gap == 0
95
+ ), "Air gap already present in the tip."
96
+ self.last_liquid_and_air_gap_in_tip.air_gap = volume
97
+
98
+ def delete_air_gap(self, volume: float) -> None:
99
+ assert (
100
+ self.last_liquid_and_air_gap_in_tip.air_gap == volume
101
+ ), "Last air gap volume doe not match the volume being removed"
102
+ self.last_liquid_and_air_gap_in_tip.air_gap = 0
103
+
104
+ def delete_last_air_gap_and_liquid(self) -> None:
105
+ air_gap_in_tip = self.last_liquid_and_air_gap_in_tip.air_gap
106
+ liquid_in_tip = self.last_liquid_and_air_gap_in_tip.liquid
107
+ if air_gap_in_tip:
108
+ self.delete_air_gap(air_gap_in_tip)
109
+ if liquid_in_tip:
110
+ self.delete_liquid(volume=liquid_in_tip)
111
+
112
+
113
+ class TransferType(Enum):
114
+ ONE_TO_ONE = "one_to_one"
115
+ MANY_TO_ONE = "many_to_one"
116
+ ONE_TO_MANY = "one_to_many"
117
+
118
+
119
+ class TransferComponentsExecutor:
120
+ def __init__(
121
+ self,
122
+ instrument_core: InstrumentCore,
123
+ transfer_properties: TransferProperties,
124
+ target_location: Union[Location, TrashBin, WasteChute],
125
+ target_well: Optional[WellCore],
126
+ tip_state: TipState,
127
+ transfer_type: TransferType,
128
+ ) -> None:
129
+ """Create a TransferComponentsExecutor instance.
130
+
131
+ One instance should be created to execute all the steps inside each of the
132
+ liquid class' transfer components- aspirate, dispense and multi-dispense.
133
+ The state of the TransferComponentsExecutor instance is expected to be valid
134
+ only for the component it was created.
135
+
136
+ For example, if we want to execute all the steps (submerge, dispense, retract, etc)
137
+ related to the 'dispense' component of a liquid-class based transfer, the class
138
+ will be used to initialize info about the dispense by assigning values
139
+ to class attributes as follows-
140
+ - target_location: the dispense location
141
+ - target_well: the well associated with dispense location, will be None when the
142
+ target_location argument is a TrashBin or WasteChute
143
+ - tip_state: the state of the tip before dispense component steps are executed
144
+ - transfer_type: whether the dispense component is being called as a part of a
145
+ 1-to-1 transfer or a consolidation or a distribution
146
+
147
+ These attributes will remain the same throughout the component's execution,
148
+ except `tip_state`, which will keep updating as fluids are handled.
149
+ """
150
+ self._instrument = instrument_core
151
+ self._transfer_properties = transfer_properties
152
+ self._target_location = target_location
153
+ self._target_well = target_well
154
+ self._tip_state: TipState = deepcopy(tip_state) # don't modify caller's object
155
+ self._transfer_type: TransferType = transfer_type
156
+
157
+ @property
158
+ def tip_state(self) -> TipState:
159
+ """Return the tip state."""
160
+ return self._tip_state
161
+
162
+ def submerge(
163
+ self,
164
+ submerge_properties: Submerge,
165
+ post_submerge_action: Literal["aspirate", "dispense"],
166
+ ) -> None:
167
+ """Execute submerge steps.
168
+
169
+ 1. move to position shown by positionReference + offset (should practically be a point outside/above the liquid).
170
+ Should raise an error if this point is inside the liquid?
171
+ For liquid meniscus this is easy to tell. Can’t be below meniscus
172
+ For reference pos of anything else, do not allow submerge position to be below aspirate position
173
+ 2. move to aspirate/dispense position at desired speed
174
+ 3. delay
175
+
176
+ If target location is a trash bin or waste chute, the pipette will move to the disposal location given,
177
+ remove air gap and delay
178
+ """
179
+ submerge_start_location: Union[Location, TrashBin, WasteChute]
180
+ if isinstance(self._target_location, Location):
181
+ assert self._target_well is not None
182
+ submerge_start_point = absolute_point_from_position_reference_and_offset(
183
+ well=self._target_well,
184
+ well_volume_difference=0,
185
+ position_reference=submerge_properties.start_position.position_reference,
186
+ offset=submerge_properties.start_position.offset,
187
+ mount=self._instrument.get_mount(),
188
+ )
189
+ submerge_start_location = Location(
190
+ point=submerge_start_point, labware=self._target_location.labware
191
+ )
192
+ tx_utils.raise_if_location_inside_liquid(
193
+ location=submerge_start_location,
194
+ well_core=self._target_well,
195
+ location_check_descriptors=LocationCheckDescriptors(
196
+ location_type="submerge start",
197
+ pipetting_action=post_submerge_action,
198
+ ),
199
+ logger=log,
200
+ )
201
+ else:
202
+ submerge_start_location = self._target_location
203
+
204
+ self._instrument.move_to(
205
+ location=submerge_start_location,
206
+ well_core=self._target_well,
207
+ force_direct=False,
208
+ minimum_z_height=None,
209
+ speed=None,
210
+ )
211
+ self._remove_air_gap(location=submerge_start_location)
212
+ if isinstance(self._target_location, Location):
213
+ self._instrument.move_to(
214
+ location=self._target_location,
215
+ well_core=self._target_well,
216
+ force_direct=True,
217
+ minimum_z_height=None,
218
+ speed=submerge_properties.speed,
219
+ )
220
+
221
+ if submerge_properties.delay.enabled and submerge_properties.delay.duration:
222
+ self._instrument.delay(submerge_properties.delay.duration)
223
+
224
+ def aspirate_and_wait(self, volume: float) -> None:
225
+ """Aspirate according to aspirate properties and wait if enabled."""
226
+ # TODO: handle volume correction
227
+ assert (
228
+ isinstance(self._target_location, Location)
229
+ and self._target_well is not None
230
+ )
231
+ aspirate_props = self._transfer_properties.aspirate
232
+ correction_volume = aspirate_props.correction_by_volume.get_for_volume(
233
+ self._instrument.get_current_volume() + volume
234
+ )
235
+ self._instrument.aspirate(
236
+ location=self._target_location,
237
+ well_core=None,
238
+ volume=volume,
239
+ rate=1,
240
+ flow_rate=aspirate_props.flow_rate_by_volume.get_for_volume(volume),
241
+ in_place=True,
242
+ correction_volume=correction_volume,
243
+ )
244
+ self._tip_state.append_liquid(volume)
245
+ delay_props = aspirate_props.delay
246
+ if delay_props.enabled and delay_props.duration:
247
+ self._instrument.delay(delay_props.duration)
248
+
249
+ def dispense_and_wait(
250
+ self,
251
+ dispense_properties: Union[SingleDispenseProperties, MultiDispenseProperties],
252
+ volume: float,
253
+ push_out_override: Optional[float],
254
+ ) -> None:
255
+ """Dispense according to dispense properties and wait if enabled."""
256
+ current_vol = self._instrument.get_current_volume()
257
+ check_current_volume_before_dispensing(
258
+ current_volume=current_vol, dispense_volume=volume
259
+ )
260
+ correction_volume = dispense_properties.correction_by_volume.get_for_volume(
261
+ current_vol - volume
262
+ )
263
+ self._instrument.dispense(
264
+ location=self._target_location,
265
+ well_core=None,
266
+ volume=volume,
267
+ rate=1,
268
+ flow_rate=dispense_properties.flow_rate_by_volume.get_for_volume(volume),
269
+ in_place=True,
270
+ push_out=push_out_override,
271
+ correction_volume=correction_volume,
272
+ )
273
+ if push_out_override:
274
+ # If a push out was performed, we need to reset the plunger before we can aspirate again
275
+ self._tip_state.ready_to_aspirate = False
276
+ self._tip_state.delete_liquid(volume)
277
+ dispense_delay = dispense_properties.delay
278
+ if dispense_delay.enabled and dispense_delay.duration:
279
+ self._instrument.delay(dispense_delay.duration)
280
+
281
+ def mix(self, mix_properties: MixProperties, last_dispense_push_out: bool) -> None:
282
+ """Execute mix steps.
283
+
284
+ 1. Use same flow rates and delays as aspirate and dispense
285
+ 2. Do [(aspirate + dispense) x repetitions] at the same position
286
+ 3. Do NOT push out at the end of dispense
287
+ 4. USE the delay property from aspirate & dispense during mix as well (flow rate and delay are coordinated with each other)
288
+ 5. Do not mix during consolidation
289
+ NOTE: For most of our built-in definitions, we will keep _mix_ off because it is a very application specific thing.
290
+ We should mention in our docs that users should adjust this property according to their application.
291
+ """
292
+ if not mix_properties.enabled or not isinstance(
293
+ self._target_location, Location
294
+ ):
295
+ return
296
+ # Assertion only for mypy purposes
297
+ assert (
298
+ mix_properties.repetitions is not None
299
+ and mix_properties.volume is not None
300
+ and self._target_well is not None
301
+ )
302
+ push_out_vol = (
303
+ self._transfer_properties.dispense.push_out_by_volume.get_for_volume(
304
+ mix_properties.volume
305
+ )
306
+ )
307
+ for n in range(mix_properties.repetitions, 0, -1):
308
+ self.aspirate_and_wait(volume=mix_properties.volume)
309
+ self.dispense_and_wait(
310
+ dispense_properties=self._transfer_properties.dispense, # TODO: check that using single-dispense props during mix is correct
311
+ volume=mix_properties.volume,
312
+ push_out_override=push_out_vol
313
+ if last_dispense_push_out is True and n == 1
314
+ else 0,
315
+ )
316
+
317
+ def pre_wet(
318
+ self,
319
+ volume: float,
320
+ ) -> None:
321
+ """Do a pre-wet.
322
+
323
+ - 1 combo of aspirate + dispense at the same flow rate as specified in asp & disp and the delays in asp & disp
324
+ - Use the target volume/ volume we will be aspirating
325
+ - No push out
326
+ - No pre-wet for consolidation
327
+ """
328
+ if not self._transfer_properties.aspirate.pre_wet:
329
+ return
330
+ mix_props = MixProperties(_enabled=True, _repetitions=1, _volume=volume)
331
+ self.mix(mix_properties=mix_props, last_dispense_push_out=False)
332
+
333
+ def retract_after_aspiration(
334
+ self, volume: float, add_air_gap: Optional[bool] = True
335
+ ) -> None:
336
+ """Execute post-aspiration retraction steps.
337
+
338
+ 1. Move TO the position reference+offset AT the specified speed
339
+ Raise error if retract is below aspirate position or below the meniscus
340
+ 2. Delay
341
+ 3. Touch tip
342
+ - Move to the Z offset position
343
+ - Touch tip to the sides at the specified speed (tip moves back to the center as part of touch tip)
344
+ - Return back to the retract position
345
+ 4. Air gap
346
+ - If the retract location is at or above the safe location of
347
+ AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP, then add the air gap at the
348
+ retract location (where the pipette is already assumed to be).
349
+ - If the retract location is below the safe location, then move to
350
+ the safe location and then add the air gap.
351
+ - Air gap volume depends on the amount of liquid in the pipette.
352
+ So, if the total aspirated volume is 20, use the value for airGapByVolume[20]
353
+ Flow rate = max(aspirateFlowRate, (airGapByVolume)/sec)
354
+ - Use post-aspirate delay
355
+
356
+ Args:
357
+ volume: dispense volume
358
+ add_air_gap: whether to add an air gap before moving away from the current well.
359
+ This value is True for all retractions, except when retracting
360
+ during a multi-dispense. Value of add_air_gap during multi-dispense
361
+ will depend on whether a conditioning volume is used.
362
+ """
363
+ assert (
364
+ isinstance(self._target_location, Location)
365
+ and self._target_well is not None
366
+ )
367
+ retract_props = self._transfer_properties.aspirate.retract
368
+ retract_point = absolute_point_from_position_reference_and_offset(
369
+ well=self._target_well,
370
+ well_volume_difference=0,
371
+ position_reference=retract_props.end_position.position_reference,
372
+ offset=retract_props.end_position.offset,
373
+ mount=self._instrument.get_mount(),
374
+ )
375
+ retract_location = Location(
376
+ retract_point, labware=self._target_location.labware
377
+ )
378
+ tx_utils.raise_if_location_inside_liquid(
379
+ location=retract_location,
380
+ well_core=self._target_well,
381
+ location_check_descriptors=LocationCheckDescriptors(
382
+ location_type="retract end",
383
+ pipetting_action="aspirate",
384
+ ),
385
+ logger=log,
386
+ )
387
+ self._instrument.move_to(
388
+ location=retract_location,
389
+ well_core=self._target_well,
390
+ force_direct=True,
391
+ minimum_z_height=None,
392
+ speed=retract_props.speed,
393
+ )
394
+ retract_delay = retract_props.delay
395
+ if retract_delay.enabled and retract_delay.duration:
396
+ self._instrument.delay(retract_delay.duration)
397
+ touch_tip_props = retract_props.touch_tip
398
+ if touch_tip_props.enabled:
399
+ assert (
400
+ touch_tip_props.speed is not None
401
+ and touch_tip_props.z_offset is not None
402
+ and touch_tip_props.mm_from_edge is not None
403
+ )
404
+ self._instrument.touch_tip(
405
+ location=retract_location,
406
+ well_core=self._target_well,
407
+ radius=1,
408
+ z_offset=touch_tip_props.z_offset,
409
+ speed=touch_tip_props.speed,
410
+ mm_from_edge=touch_tip_props.mm_from_edge,
411
+ )
412
+ self._instrument.move_to(
413
+ location=retract_location,
414
+ well_core=self._target_well,
415
+ force_direct=True,
416
+ minimum_z_height=None,
417
+ # Full speed because the tip will already be out of the liquid
418
+ speed=None,
419
+ )
420
+ # For consolidate, we need to know the total amount that is in the pipette
421
+ # since this may not be the first aspirate
422
+ if self._transfer_type == TransferType.MANY_TO_ONE:
423
+ volume_for_air_gap = self._instrument.get_current_volume()
424
+ else:
425
+ volume_for_air_gap = volume
426
+ if add_air_gap:
427
+ # If we need to add air gap, move to a safe location above the well if
428
+ # the retract location is not already at or above this safe location
429
+ if (
430
+ retract_location.point.z
431
+ < self._target_well.get_top(AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP).z
432
+ ):
433
+ self._instrument.move_to(
434
+ location=Location(
435
+ point=Point(
436
+ retract_location.point.x,
437
+ retract_location.point.y,
438
+ self._target_well.get_top(
439
+ AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP
440
+ ).z,
441
+ ),
442
+ labware=retract_location.labware,
443
+ ),
444
+ well_core=self._target_well,
445
+ force_direct=True,
446
+ minimum_z_height=None,
447
+ # Full speed because the tip will already be out of the liquid
448
+ speed=None,
449
+ )
450
+ self._add_air_gap(
451
+ air_gap_volume=self._transfer_properties.aspirate.retract.air_gap_by_volume.get_for_volume(
452
+ volume_for_air_gap
453
+ )
454
+ )
455
+
456
+ def retract_after_dispensing(
457
+ self,
458
+ trash_location: Union[Location, TrashBin, WasteChute],
459
+ source_location: Optional[Location],
460
+ source_well: Optional[WellCore],
461
+ add_final_air_gap: bool,
462
+ ) -> None:
463
+ """Execute post-dispense retraction steps.
464
+ 1. Position ref+offset is the ending position. Move to this position using specified speed
465
+ 2. If blowout is enabled and “destination”
466
+ - Do blow-out (at the retract position)
467
+ - Leave plunger down
468
+ 3. Touch-tip in the destination well.
469
+ 4. If not ready-to-aspirate
470
+ - Prepare-to-aspirate (at the retract position)
471
+ 5. Air-gap (at the retract position)
472
+ - This air gap is for preventing any stray droplets from falling while moving the pipette.
473
+ It will be performed out of caution even if we just did a blow_out and should *hypothetically*
474
+ have no liquid left in the tip.
475
+ - This air gap will be removed at the next aspirate.
476
+ If this is the last step of the transfer, and we aren't dropping the tip off,
477
+ then the air gap will be left as is(?).
478
+ 6. If blowout is “source” or “trash”
479
+ - Move to location (top of Well)
480
+ - Do blow-out (top of well)
481
+ - Do touch-tip AGAIN at the source well (if blowout in a non-trash location)
482
+ - Prepare-to-aspirate (top of well)
483
+ - Do air-gap (top of well)
484
+ 7. If drop tip, move to drop tip location, drop tip
485
+
486
+ If target location is a trash bin or waste chute, the retract movement step is skipped along with touch tip,
487
+ even if it is enabled.
488
+ """
489
+ retract_props = self._transfer_properties.dispense.retract
490
+
491
+ retract_location: Union[Location, TrashBin, WasteChute]
492
+ if isinstance(self._target_location, Location):
493
+ assert self._target_well is not None
494
+ retract_point = absolute_point_from_position_reference_and_offset(
495
+ well=self._target_well,
496
+ well_volume_difference=0,
497
+ position_reference=retract_props.end_position.position_reference,
498
+ offset=retract_props.end_position.offset,
499
+ mount=self._instrument.get_mount(),
500
+ )
501
+ retract_location = Location(
502
+ retract_point, labware=self._target_location.labware
503
+ )
504
+ tx_utils.raise_if_location_inside_liquid(
505
+ location=retract_location,
506
+ well_core=self._target_well,
507
+ location_check_descriptors=LocationCheckDescriptors(
508
+ location_type="retract end",
509
+ pipetting_action="dispense",
510
+ ),
511
+ logger=log,
512
+ )
513
+ self._instrument.move_to(
514
+ location=retract_location,
515
+ well_core=self._target_well,
516
+ force_direct=True,
517
+ minimum_z_height=None,
518
+ speed=retract_props.speed,
519
+ )
520
+ else:
521
+ retract_location = self._target_location
522
+
523
+ # TODO should we delay here for a trash despite not having a "retract"?
524
+ retract_delay = retract_props.delay
525
+ if retract_delay.enabled and retract_delay.duration:
526
+ self._instrument.delay(retract_delay.duration)
527
+
528
+ blowout_props = retract_props.blowout
529
+ if (
530
+ blowout_props.enabled
531
+ and blowout_props.location == BlowoutLocation.DESTINATION
532
+ ):
533
+ assert blowout_props.flow_rate is not None
534
+ self._instrument.set_flow_rate(blow_out=blowout_props.flow_rate)
535
+ self._instrument.blow_out(
536
+ location=retract_location,
537
+ well_core=None,
538
+ in_place=True,
539
+ )
540
+ self._tip_state.ready_to_aspirate = False
541
+ is_final_air_gap = (
542
+ blowout_props.enabled
543
+ and blowout_props.location == BlowoutLocation.DESTINATION
544
+ ) or not blowout_props.enabled
545
+
546
+ if is_final_air_gap and not add_final_air_gap:
547
+ air_gap_volume = 0.0
548
+ else:
549
+ air_gap_volume = retract_props.air_gap_by_volume.get_for_volume(0)
550
+ # Regardless of the blowout location, do touch tip and air gap
551
+ # when leaving the dispense well. If this will be the final air gap, i.e,
552
+ # we won't be moving to a Trash or a Source for Blowout after this air gap,
553
+ # then skip the final air gap if we have been told to do so.
554
+ self._do_touch_tip_and_air_gap_after_dispense(
555
+ touch_tip_properties=retract_props.touch_tip,
556
+ location=retract_location,
557
+ well=self._target_well,
558
+ air_gap_volume=air_gap_volume,
559
+ )
560
+
561
+ if (
562
+ blowout_props.enabled
563
+ and blowout_props.location != BlowoutLocation.DESTINATION
564
+ ):
565
+ assert blowout_props.flow_rate is not None
566
+ self._instrument.set_flow_rate(blow_out=blowout_props.flow_rate)
567
+ blowout_touch_tip_props = retract_props.touch_tip
568
+ touch_tip_and_air_gap_location: Union[Location, TrashBin, WasteChute]
569
+ if blowout_props.location == BlowoutLocation.SOURCE:
570
+ if source_location is None or source_well is None:
571
+ raise RuntimeError(
572
+ "Blowout location is 'source' but source location &/or well is not provided."
573
+ )
574
+ # TODO: check if we should add a blowout location z-offset in liq class definition
575
+ self._instrument.blow_out(
576
+ location=Location(
577
+ source_well.get_top(0), labware=source_location.labware
578
+ ),
579
+ well_core=source_well,
580
+ in_place=False,
581
+ )
582
+ touch_tip_and_air_gap_location = Location(
583
+ source_well.get_top(0), labware=source_location.labware
584
+ )
585
+ touch_tip_and_air_gap_well = source_well
586
+ # Skip touch tip if blowing out at the SOURCE and it's untouchable:
587
+ if (
588
+ "touchTipDisabled"
589
+ in source_location.labware.quirks_from_any_parent()
590
+ ):
591
+ blowout_touch_tip_props = replace(blowout_touch_tip_props)
592
+ blowout_touch_tip_props.enabled = False
593
+ else:
594
+ self._instrument.blow_out(
595
+ location=trash_location,
596
+ well_core=None,
597
+ in_place=False,
598
+ )
599
+ touch_tip_and_air_gap_location = trash_location
600
+ touch_tip_and_air_gap_well = (
601
+ # We have already established that trash location of `Location` type
602
+ # has its `labware` as `Well` type.
603
+ trash_location.labware.as_well()._core # type: ignore[assignment]
604
+ if isinstance(trash_location, Location)
605
+ else None
606
+ )
607
+ # A non-multi-dispense blowout will only have air and maybe droplets in the tip
608
+ # since we only blowout after dispensing the full tip contents.
609
+ # So delete the air gap from tip state
610
+ last_air_gap = self._tip_state.last_liquid_and_air_gap_in_tip.air_gap
611
+ self._tip_state.delete_air_gap(last_air_gap)
612
+ self._tip_state.ready_to_aspirate = False
613
+
614
+ air_gap_volume = (
615
+ retract_props.air_gap_by_volume.get_for_volume(0)
616
+ if add_final_air_gap
617
+ else 0.0
618
+ )
619
+ # Do touch tip and air gap again after blowing out into source well or trash
620
+ self._do_touch_tip_and_air_gap_after_dispense(
621
+ touch_tip_properties=blowout_touch_tip_props,
622
+ location=touch_tip_and_air_gap_location,
623
+ well=touch_tip_and_air_gap_well,
624
+ air_gap_volume=air_gap_volume,
625
+ )
626
+
627
+ def retract_during_multi_dispensing( # noqa: C901
628
+ self,
629
+ trash_location: Union[Location, TrashBin, WasteChute],
630
+ source_location: Optional[Location],
631
+ source_well: Optional[WellCore],
632
+ conditioning_volume: float,
633
+ add_final_air_gap: bool,
634
+ is_last_retract: bool,
635
+ ) -> None:
636
+ """Execute post-dispense retraction steps when the dispense is a part of a multi-dispense.
637
+
638
+ Args:
639
+ trash_location: Location where we can drop tips or blowout, if set to do so
640
+ source_location: Location where we can blowout, if set to do so
641
+ source_well: Well where we can blowout, if set to do so
642
+ conditioning_volume: Conditioning volume used for this multi-dispense. Can be 0
643
+ add_final_air_gap: Whether we should add the final air gap of the step
644
+ is_last_retract: Whether this is the last retract of the multi-dispense steps,
645
+ i.e., this is part of the last dispense in the series of consecutive dispenses.
646
+ This dispense might not be the last dispense of the entire distribution.
647
+
648
+ This function is mostly similar to the single-dispense retract function except
649
+ that it handles air gaps differently based on the disposal volume, conditioning volume
650
+ and whether we are moving to another dispense or going back to the source.
651
+ """
652
+ assert (
653
+ isinstance(self._target_location, Location)
654
+ and self._target_well is not None
655
+ )
656
+ assert self._transfer_properties.multi_dispense is not None
657
+
658
+ retract_props = self._transfer_properties.multi_dispense.retract
659
+ retract_point = absolute_point_from_position_reference_and_offset(
660
+ well=self._target_well,
661
+ well_volume_difference=0,
662
+ position_reference=retract_props.end_position.position_reference,
663
+ offset=retract_props.end_position.offset,
664
+ mount=self._instrument.get_mount(),
665
+ )
666
+ retract_location = Location(
667
+ retract_point, labware=self._target_location.labware
668
+ )
669
+ tx_utils.raise_if_location_inside_liquid(
670
+ location=retract_location,
671
+ well_core=self._target_well,
672
+ location_check_descriptors=LocationCheckDescriptors(
673
+ location_type="retract end",
674
+ pipetting_action="dispense",
675
+ ),
676
+ logger=log,
677
+ )
678
+ self._instrument.move_to(
679
+ location=retract_location,
680
+ well_core=self._target_well,
681
+ force_direct=True,
682
+ minimum_z_height=None,
683
+ speed=retract_props.speed,
684
+ )
685
+ retract_delay = retract_props.delay
686
+ if retract_delay.enabled and retract_delay.duration:
687
+ self._instrument.delay(retract_delay.duration)
688
+
689
+ blowout_props = retract_props.blowout
690
+ if (
691
+ is_last_retract
692
+ and blowout_props.enabled
693
+ and blowout_props.location == BlowoutLocation.DESTINATION
694
+ ):
695
+ assert blowout_props.flow_rate is not None
696
+ self._instrument.set_flow_rate(blow_out=blowout_props.flow_rate)
697
+ self._instrument.blow_out(
698
+ location=retract_location,
699
+ well_core=None,
700
+ in_place=True,
701
+ )
702
+ # A blowout will remove all air gap and liquid (disposal volume) from the tip
703
+ # so delete them from tip state (although practically, there will not be
704
+ # any air gaps in the tip before blowing out in the destination well)
705
+ self._tip_state.delete_last_air_gap_and_liquid()
706
+ self._tip_state.ready_to_aspirate = False
707
+
708
+ # A retract will perform total of two air gaps if we need to blow out in source or trash:
709
+ # - 1st air gap: added before leaving the destination volume to go to src/ trash
710
+ # - 2nd air gap: added before leaving the blowout location to go to src or tip drop location
711
+ # But if blowout is disabled or is set to Destination well, then only one air gap
712
+ # will be added after retracting, before moving to src or tip drop location.
713
+ # `is_final_air_gap_of_current_retract` tells us whether the next air gap
714
+ # we will be adding, is going to be the last air gap of this step.
715
+ is_final_air_gap_of_current_retract = (
716
+ blowout_props.enabled
717
+ and blowout_props.location == BlowoutLocation.DESTINATION
718
+ ) or not blowout_props.enabled
719
+
720
+ # Whether we should add the next air gap depends on the cases as shown below.
721
+ # The main points when deciding this-
722
+ # - When we have used a conditioning volume, we do not want to add air gaps
723
+ # while there's still liquid in tip for dispensing
724
+ # - If we are not using conditioning volume then we want to add gaps just like
725
+ # we do during the one-to-one transfers
726
+ # - If this will be the last air gap of the step, if the above two conditions
727
+ # indicate that we should be adding an air gap, use `add_final_air_gap` as
728
+ # the final decider of whether to add the air gap.
729
+ if is_final_air_gap_of_current_retract:
730
+ if conditioning_volume > 0:
731
+ add_air_gap = is_last_retract and add_final_air_gap
732
+ else:
733
+ add_air_gap = add_final_air_gap
734
+ else:
735
+ if conditioning_volume > 0:
736
+ add_air_gap = is_last_retract
737
+ else:
738
+ add_air_gap = True
739
+
740
+ air_gap_volume = (
741
+ retract_props.air_gap_by_volume.get_for_volume(
742
+ self.tip_state.last_liquid_and_air_gap_in_tip.liquid
743
+ )
744
+ if add_air_gap
745
+ else 0.0
746
+ )
747
+
748
+ # Regardless of the blowout location, do touch tip
749
+ # when leaving the dispense well.
750
+ # Add an air gap depending on conditioning volume + whether this is
751
+ # the last step of a multi-dispense sequence + whether this is the last step
752
+ # of the entire liquid distribution.
753
+ self._do_touch_tip_and_air_gap_after_dispense(
754
+ touch_tip_properties=retract_props.touch_tip,
755
+ location=retract_location,
756
+ well=self._target_well,
757
+ air_gap_volume=air_gap_volume,
758
+ )
759
+
760
+ if (
761
+ is_last_retract # We can do a blowout only on the last multi-dispense step
762
+ and blowout_props.enabled
763
+ and blowout_props.location != BlowoutLocation.DESTINATION
764
+ ):
765
+ assert blowout_props.flow_rate is not None
766
+ self._instrument.set_flow_rate(blow_out=blowout_props.flow_rate)
767
+ blowout_touch_tip_props = retract_props.touch_tip
768
+ touch_tip_and_air_gap_location: Union[Location, TrashBin, WasteChute]
769
+ if blowout_props.location == BlowoutLocation.SOURCE:
770
+ if source_location is None or source_well is None:
771
+ raise RuntimeError(
772
+ "Blowout location is 'source' but source location &/or well is not provided."
773
+ )
774
+ # TODO: check if we should add a blowout location z-offset in liq class definition
775
+ self._instrument.blow_out(
776
+ location=Location(
777
+ source_well.get_top(0), labware=source_location.labware
778
+ ),
779
+ well_core=source_well,
780
+ in_place=False,
781
+ )
782
+ touch_tip_and_air_gap_location = Location(
783
+ source_well.get_top(0), labware=source_location.labware
784
+ )
785
+ touch_tip_and_air_gap_well = source_well
786
+ # Skip touch tip if blowing out at the SOURCE and it's untouchable:
787
+ if (
788
+ "touchTipDisabled"
789
+ in source_location.labware.quirks_from_any_parent()
790
+ ):
791
+ blowout_touch_tip_props = replace(blowout_touch_tip_props)
792
+ blowout_touch_tip_props.enabled = False
793
+ else:
794
+ self._instrument.blow_out(
795
+ location=trash_location,
796
+ well_core=None,
797
+ in_place=False,
798
+ )
799
+ touch_tip_and_air_gap_location = trash_location
800
+ touch_tip_and_air_gap_well = (
801
+ # We have already established that trash location of `Location` type
802
+ # has its `labware` as `Well` type.
803
+ trash_location.labware.as_well()._core # type: ignore[assignment]
804
+ if isinstance(trash_location, Location)
805
+ else None
806
+ )
807
+ # A blowout will remove all air gap and liquid (disposal volume) from the tip
808
+ # so delete them from tip state
809
+ self._tip_state.delete_last_air_gap_and_liquid()
810
+ self._tip_state.ready_to_aspirate = False
811
+
812
+ if (
813
+ # Same check as before for when it's the final air gap of current retract
814
+ conditioning_volume > 0
815
+ and is_last_retract
816
+ and add_final_air_gap
817
+ ):
818
+ # The volume in tip at this point should be 0uL
819
+ air_gap_volume = retract_props.air_gap_by_volume.get_for_volume(0)
820
+ else:
821
+ air_gap_volume = 0
822
+ # Do touch tip and air gap again after blowing out into source well or trash
823
+ self._do_touch_tip_and_air_gap_after_dispense(
824
+ touch_tip_properties=blowout_touch_tip_props,
825
+ location=touch_tip_and_air_gap_location,
826
+ well=touch_tip_and_air_gap_well,
827
+ air_gap_volume=air_gap_volume,
828
+ )
829
+
830
+ def _do_touch_tip_and_air_gap_after_dispense(
831
+ self,
832
+ touch_tip_properties: TouchTipProperties,
833
+ location: Union[Location, TrashBin, WasteChute],
834
+ well: Optional[WellCore],
835
+ air_gap_volume: float,
836
+ ) -> None:
837
+ """Perform touch tip and air gap as part of post-dispense retract.
838
+
839
+ This function can be invoked up to 2 times for each dispense:
840
+ 1) Once for touching tip at the dispense location.
841
+ 2) Then again in the blowout location if it is not the dispense location.
842
+ For case (2), the caller should disable touch-tip in touch_tip_properties
843
+ if the blowout location is not touchable (such as reservoirs).
844
+
845
+ If the retract location is at or above the safe location of
846
+ AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP, then add the air gap at the retract location
847
+ (where the pipette is already assumed to be at).
848
+
849
+ If the retract location is below the safe location, then move to the safe location
850
+ and then add the air gap.
851
+
852
+ Note: if the plunger needs to be adjusted to prepare for aspirate, it will be done
853
+ at the same location where the air gap will be added.
854
+ """
855
+ if touch_tip_properties.enabled:
856
+ assert (
857
+ touch_tip_properties.speed is not None
858
+ and touch_tip_properties.z_offset is not None
859
+ and touch_tip_properties.mm_from_edge is not None
860
+ )
861
+ # TODO:, check that when blow out is a non-dest-well,
862
+ # whether the touch tip params from transfer props should be used for
863
+ # both dest-well touch tip and non-dest-well touch tip.
864
+ if isinstance(location, Location) and well is not None:
865
+ self._instrument.touch_tip(
866
+ location=location,
867
+ well_core=well,
868
+ radius=1,
869
+ z_offset=touch_tip_properties.z_offset,
870
+ speed=touch_tip_properties.speed,
871
+ mm_from_edge=touch_tip_properties.mm_from_edge,
872
+ )
873
+
874
+ # Move back to the 'retract' position
875
+ self._instrument.move_to(
876
+ location=location,
877
+ well_core=well,
878
+ force_direct=True,
879
+ minimum_z_height=None,
880
+ # Full speed because the tip will already be out of the liquid
881
+ speed=None,
882
+ )
883
+ if air_gap_volume > 0 or not self._tip_state.ready_to_aspirate:
884
+ # If we need to move the plunger up either to prepare for aspirate or to add air gap,
885
+ # move to a safe location above the well if the retract location is not already
886
+ # at or above this safe location
887
+ if isinstance(location, Location):
888
+ assert well is not None # For mypy purposes only
889
+ if (
890
+ location.point.z
891
+ < well.get_top(AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP).z
892
+ ):
893
+ self._instrument.move_to(
894
+ location=Location(
895
+ point=Point(
896
+ location.point.x,
897
+ location.point.y,
898
+ well.get_top(AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP).z,
899
+ ),
900
+ labware=location.labware,
901
+ ),
902
+ well_core=well,
903
+ force_direct=True,
904
+ minimum_z_height=None,
905
+ speed=None,
906
+ )
907
+ else:
908
+ if (
909
+ location.offset.z
910
+ < location.top(
911
+ x=0, y=0, z=AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP
912
+ ).offset.z
913
+ ):
914
+ self._instrument.move_to(
915
+ location=location.top(
916
+ x=location.offset.x,
917
+ y=location.offset.y,
918
+ z=AIR_GAP_LOC_Z_OFFSET_FROM_WELL_TOP,
919
+ ),
920
+ well_core=None,
921
+ force_direct=True,
922
+ minimum_z_height=None,
923
+ speed=None,
924
+ )
925
+
926
+ if not self._tip_state.ready_to_aspirate:
927
+ self._instrument.prepare_to_aspirate()
928
+ self._tip_state.ready_to_aspirate = True
929
+ if air_gap_volume > 0:
930
+ self._add_air_gap(air_gap_volume=air_gap_volume)
931
+
932
+ def _add_air_gap(
933
+ self,
934
+ air_gap_volume: float,
935
+ ) -> None:
936
+ """Add an air gap."""
937
+ if air_gap_volume == 0:
938
+ return
939
+ aspirate_props = self._transfer_properties.aspirate
940
+ correction_volume = aspirate_props.correction_by_volume.get_for_volume(
941
+ self._instrument.get_current_volume() + air_gap_volume
942
+ )
943
+ # The minimum flow rate should be air_gap_volume per second
944
+ flow_rate = max(
945
+ aspirate_props.flow_rate_by_volume.get_for_volume(air_gap_volume),
946
+ air_gap_volume,
947
+ )
948
+ self._instrument.air_gap_in_place(
949
+ volume=air_gap_volume,
950
+ flow_rate=flow_rate,
951
+ correction_volume=correction_volume,
952
+ )
953
+ delay_props = aspirate_props.delay
954
+ if delay_props.enabled and delay_props.duration:
955
+ self._instrument.delay(delay_props.duration)
956
+ self._tip_state.append_air_gap(air_gap_volume)
957
+
958
+ def _remove_air_gap(self, location: Union[Location, TrashBin, WasteChute]) -> None:
959
+ """Remove a previously added air gap."""
960
+ last_air_gap = self._tip_state.last_liquid_and_air_gap_in_tip.air_gap
961
+ dispense_props = self._transfer_properties.dispense
962
+ self._instrument.remove_air_gap_during_transfer_with_liquid_class(
963
+ last_air_gap=last_air_gap,
964
+ dispense_props=dispense_props,
965
+ location=location,
966
+ )
967
+ self._tip_state.delete_air_gap(last_air_gap)
968
+
969
+
970
+ def absolute_point_from_position_reference_and_offset(
971
+ well: WellCore,
972
+ well_volume_difference: float,
973
+ position_reference: PositionReference,
974
+ offset: Coordinate,
975
+ mount: Mount,
976
+ ) -> Point:
977
+ """Return the absolute point, given the well, the position reference and offset.
978
+
979
+ If using meniscus as the position reference, well_volume_difference should be specified.
980
+ `well_volume_difference` is the expected *difference* in well volume we want to consider
981
+ when estimating the height of the liquid meniscus after an aspirate/ dispense.
982
+ So, for liquid height estimation after an aspirate, well_volume_difference is
983
+ expected to be a -ve value while for a dispense, it will be a +ve value.
984
+ """
985
+ match position_reference:
986
+ case PositionReference.WELL_TOP:
987
+ reference_point = well.get_top(0)
988
+ case PositionReference.WELL_BOTTOM:
989
+ reference_point = well.get_bottom(0)
990
+ case PositionReference.WELL_CENTER:
991
+ reference_point = well.get_center()
992
+ case PositionReference.LIQUID_MENISCUS:
993
+ estimated_liquid_height = well.estimate_liquid_height_after_pipetting(
994
+ mount=mount,
995
+ operation_volume=well_volume_difference,
996
+ )
997
+ if isinstance(estimated_liquid_height, (float, int)):
998
+ reference_point = well.get_bottom(z_offset=estimated_liquid_height)
999
+ else:
1000
+ # If estimated liquid height gives a SimulatedProbeResult then
1001
+ # assume meniscus is at well center.
1002
+ # Will this cause more harm than good? Is there a better alternative to this?
1003
+ reference_point = well.get_center()
1004
+ case _:
1005
+ raise ValueError(f"Unknown position reference {position_reference}")
1006
+ return reference_point + Point(offset.x, offset.y, offset.z)