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,972 @@
1
+ import enum
2
+ from typing import (
3
+ Any,
4
+ Dict,
5
+ List,
6
+ Optional,
7
+ Union,
8
+ NamedTuple,
9
+ Callable,
10
+ Generator,
11
+ Iterator,
12
+ Sequence,
13
+ Tuple,
14
+ TypedDict,
15
+ TypeAlias,
16
+ TYPE_CHECKING,
17
+ )
18
+ from opentrons.protocol_api.labware import Labware, Well
19
+ from opentrons import types
20
+ from opentrons.protocols.api_support.types import APIVersion
21
+
22
+ from . import common as tx_commons
23
+ from ..common import Mix, MixOpts, MixStrategy
24
+
25
+ AdvancedLiquidHandling = Union[
26
+ Well,
27
+ types.Location,
28
+ Sequence[Union[Well, types.Location]],
29
+ Sequence[Sequence[Well]],
30
+ ]
31
+
32
+
33
+ class TransferStep(TypedDict):
34
+ method: str
35
+ args: Optional[List[Any]]
36
+ kwargs: Optional[Dict[Any, Any]]
37
+
38
+
39
+ if TYPE_CHECKING:
40
+ from opentrons.protocol_api import InstrumentContext
41
+
42
+ _PARTIAL_TIP_SUPPORT_ADDED = APIVersion(2, 18)
43
+ """The version after which partial tip support and nozzle maps were made available."""
44
+
45
+
46
+ class DropTipStrategy(enum.Enum):
47
+ TRASH = enum.auto()
48
+ RETURN = enum.auto()
49
+
50
+
51
+ class TouchTipStrategy(enum.Enum):
52
+ NEVER = enum.auto()
53
+ ALWAYS = enum.auto()
54
+
55
+
56
+ class BlowOutStrategy(enum.Enum):
57
+ NONE = enum.auto()
58
+ TRASH = enum.auto()
59
+ DEST = enum.auto()
60
+ SOURCE = enum.auto()
61
+ CUSTOM_LOCATION = enum.auto()
62
+
63
+
64
+ class TransferMode(enum.Enum):
65
+ DISTRIBUTE = enum.auto()
66
+ CONSOLIDATE = enum.auto()
67
+ TRANSFER = enum.auto()
68
+
69
+
70
+ class Transfer(NamedTuple):
71
+ """
72
+ Options pertaining to behavior of the transfer.
73
+
74
+ """
75
+
76
+ new_tip: types.TransferTipPolicy = types.TransferTipPolicy.ONCE
77
+ air_gap: float = 0
78
+ carryover: bool = True
79
+ gradient_function: Optional[Callable[[float], float]] = None
80
+ disposal_volume: float = 0
81
+ mix_strategy: MixStrategy = MixStrategy.NEVER
82
+ drop_tip_strategy: DropTipStrategy = DropTipStrategy.TRASH
83
+ blow_out_strategy: BlowOutStrategy = BlowOutStrategy.NONE
84
+ touch_tip_strategy: TouchTipStrategy = TouchTipStrategy.NEVER
85
+
86
+
87
+ Transfer.new_tip.__doc__ = """
88
+ Control when or if to pick up tip during a transfer
89
+
90
+ :py:attr:`types.TransferTipPolicy.ALWAYS`
91
+ Drop and pick up a new tip after each dispense.
92
+
93
+ :py:attr:`types.TransferTipPolicy.ONCE`
94
+ Pick up tip at the beginning of the transfer and use it
95
+ throughout the transfer. This would speed up the transfer.
96
+
97
+ :py:attr:`types.TransferTipPolicy.NEVER`
98
+ Do not ever pick up or drop tip. The protocol should explicitly
99
+ pick up a tip before transfer and drop it afterwards.
100
+
101
+ To customize where to drop tip, see :py:attr:`.drop_tip_strategy`.
102
+ To customize the behavior of pickup tip, see
103
+ :py:attr:`.TransferOptions.pick_up_tip`.
104
+ """
105
+
106
+ Transfer.air_gap.__doc__ = """
107
+ Controls the volume (in uL) of air gap aspirated when moving to
108
+ dispense.
109
+
110
+ Adding an air gap would slow down a transfer since less liquid will
111
+ now fit in the pipette but it prevents the loss of liquid while
112
+ moving between wells.
113
+ """
114
+
115
+ Transfer.carryover.__doc__ = """
116
+ Controls whether volumes larger than pipette's max volume will be
117
+ split into smaller volumes.
118
+ """
119
+
120
+ Transfer.gradient_function.__doc__ = """
121
+ Specify a nonlinear gradient for volumes.
122
+
123
+ This should be a function that takes a single float between 0 and 1
124
+ and returns a single float between 0 and 1. This function is used
125
+ to determine the path the transfer takes between the volume
126
+ gradient minimum and maximum if the transfer volume is specified as
127
+ a gradient. For instance, specifying the function as
128
+
129
+ .. code-block:: python
130
+
131
+ def gradient(a):
132
+ if a > 0.5:
133
+ return 1.0
134
+ else:
135
+ return 0.0
136
+
137
+ would transfer the minimum volume of the gradient to the first half
138
+ of the target wells, and the maximum to the other half.
139
+ """
140
+
141
+ Transfer.disposal_volume.__doc__ = """
142
+ The amount of liquid (in uL) to aspirate as a buffer.
143
+
144
+ The remaining buffer will be blown out into the location specified
145
+ by :py:attr:`.blow_out_strategy`.
146
+
147
+ This is useful to avoid under-pipetting but can waste reagent and
148
+ slow down transfer.
149
+ """
150
+
151
+ Transfer.mix_strategy.__doc__ = """
152
+ If and when to mix during a transfer.
153
+
154
+ :py:attr:`MixStrategy.NEVER`
155
+ Do not ever perform a mix during the transfer.
156
+
157
+ :py:attr:`MixStrategy.BEFORE`
158
+ Mix before each aspirate.
159
+
160
+ :py:attr:`MixStrategy.AFTER`
161
+ Mix after each dispense.
162
+
163
+ :py:attr:`MixStrategy.BOTH`
164
+ Mix before each aspirate and after each dispense.
165
+
166
+ To customize the mix behavior, see :py:attr:`.TransferOptions.mix`
167
+ """
168
+
169
+ Transfer.drop_tip_strategy.__doc__ = """
170
+ Specifies the location to drop tip into.
171
+
172
+ :py:attr:`DropTipStrategy.TRASH`
173
+ Drop the tip into the trash container.
174
+
175
+ :py:attr:`DropTipStrategy.RETURN`
176
+ Return the tip to tiprack.
177
+ """
178
+
179
+ Transfer.blow_out_strategy.__doc__ = """
180
+ Specifies the location to blow out the liquid in the pipette to.
181
+
182
+ :py:attr:`BlowOutStrategy.TRASH`
183
+ Blow out to trash container.
184
+
185
+ :py:attr:`BlowOutStrategy.SOURCE`
186
+ Blow out into the source well in order to dispense any leftover
187
+ liquid.
188
+
189
+ :py:attr:`BlowOutStrategy.DEST`
190
+ Blow out into the destination well in order to dispense any leftover
191
+ liquid.
192
+
193
+ :py:attr:`BlowOutStrategy.CUSTOM_LOCATION`
194
+ If using any other location to blow out to. Specify the location in
195
+ :py:attr:`.TransferOptions.blow_out`.
196
+ """
197
+
198
+ Transfer.touch_tip_strategy.__doc__ = """
199
+ Controls whether to touch tip during the transfer
200
+
201
+ This helps in getting rid of any droplets clinging to the pipette
202
+ tip at the cost of slowing down the transfer.
203
+
204
+ :py:attr:`TouchTipStrategy.NEVER`
205
+ Do not touch tip ever during the transfer.
206
+
207
+ :py:attr:`TouchTipStrategy.ALWAYS`
208
+ Touch tip after each aspirate.
209
+
210
+ To customize the behavior of touch tips, see
211
+ :py:attr:`.TransferOptions.touch_tip`.
212
+ """
213
+
214
+
215
+ class PickUpTipOpts(NamedTuple):
216
+ """
217
+ Options to customize :py:attr:`.Transfer.new_tip`.
218
+
219
+ These options will be passed to
220
+ :py:meth:`InstrumentContext.pick_up_tip` when it is called during
221
+ the transfer.
222
+ """
223
+
224
+ location: Optional[types.Location] = None
225
+ presses: Optional[int] = None
226
+ increment: Optional[int] = None
227
+
228
+
229
+ PickUpTipOpts.location.__doc__ = ":py:class:`types.Location`"
230
+ PickUpTipOpts.presses.__doc__ = ":py:class:`int`"
231
+ PickUpTipOpts.increment.__doc__ = ":py:class:`int`"
232
+
233
+
234
+ Mix.mix_before.__doc__ = """
235
+ Options applied to mix before aspirate.
236
+ See :py:class:`.Mix.MixOpts`.
237
+ """
238
+
239
+ Mix.mix_after.__doc__ = """
240
+ Options applied to mix after dispense. See :py:class:`.Mix.MixOpts`.
241
+ """
242
+
243
+
244
+ class BlowOutOpts(NamedTuple):
245
+ """
246
+ Location where to blow out instead of the trash.
247
+
248
+ This location will be passed to :py:meth:`InstrumentContext.blow_out`
249
+ when called during the transfer
250
+ """
251
+
252
+ location: Optional[Union[types.Location, Well]] = None
253
+
254
+
255
+ BlowOutOpts.location.__doc__ = ":py:class:`types.Location`"
256
+
257
+
258
+ class TouchTipOpts(NamedTuple):
259
+ """
260
+ Options to customize touch tip.
261
+
262
+ These options will be passed to
263
+ :py:meth:`InstrumentContext.touch_tip` when called during the
264
+ transfer.
265
+ """
266
+
267
+ radius: Optional[float] = None
268
+ v_offset: Optional[float] = None
269
+ speed: Optional[float] = None
270
+
271
+
272
+ TouchTipOpts.radius.__doc__ = ":py:class:`float`"
273
+ TouchTipOpts.v_offset.__doc__ = ":py:class:`float`"
274
+ TouchTipOpts.speed.__doc__ = ":py:class:`float`"
275
+
276
+
277
+ class AspirateOpts(NamedTuple):
278
+ """
279
+ Option to customize aspirate rate.
280
+
281
+ This option will be passed to :py:meth:`InstrumentContext.aspirate`
282
+ when called during the transfer.
283
+ """
284
+
285
+ rate: Optional[float] = 1.0
286
+
287
+
288
+ AspirateOpts.rate.__doc__ = ":py:class:`float`"
289
+
290
+
291
+ class DispenseOpts(NamedTuple):
292
+ """
293
+ Option to customize dispense rate.
294
+
295
+ This option will be passed to :py:meth:`InstrumentContext.dispense`
296
+ when called during the transfer.
297
+ """
298
+
299
+ rate: Optional[float] = 1.0
300
+
301
+
302
+ DispenseOpts.rate.__doc__ = ":py:class:`float`"
303
+
304
+
305
+ class TransferOptions(NamedTuple):
306
+ """
307
+ All available options for a transfer, distribute or consolidate function
308
+ """
309
+
310
+ transfer: Transfer = Transfer()
311
+ pick_up_tip: PickUpTipOpts = PickUpTipOpts()
312
+ mix: Mix = Mix()
313
+ blow_out: BlowOutOpts = BlowOutOpts()
314
+ touch_tip: TouchTipOpts = TouchTipOpts()
315
+ aspirate: AspirateOpts = AspirateOpts()
316
+ dispense: DispenseOpts = DispenseOpts()
317
+
318
+
319
+ FormatDictArgs: TypeAlias = Union[
320
+ PickUpTipOpts, MixOpts, BlowOutOpts, TouchTipOpts, AspirateOpts, DispenseOpts
321
+ ]
322
+
323
+
324
+ TransferOptions.transfer.__doc__ = """
325
+ Options pertaining to behavior of the transfer.
326
+
327
+ For instance you can control how frequently to get a new tip using
328
+ :py:attr:`.Transfer.new_tip`. For documentation of all transfer options
329
+ see :py:class:`.Transfer`.
330
+ """
331
+
332
+ TransferOptions.pick_up_tip.__doc__ = """
333
+ Options used when picking up a tip during transfer.
334
+ See :py:class:`.PickUpTipOpts`.
335
+ """
336
+
337
+ TransferOptions.mix.__doc__ = """
338
+ Options to control mix behavior before aspirate and after dispense.
339
+ See :py:class:`.Mix`.
340
+ """
341
+
342
+ TransferOptions.blow_out.__doc__ = """
343
+ Option to specify custom location for blow out. See
344
+ :py:class:`.BlowOutOpts`.
345
+ """
346
+
347
+ TransferOptions.touch_tip.__doc__ = """
348
+ Options to customize touch tip. See
349
+ :py:class:`.TouchTipOpts`.
350
+ """
351
+
352
+ TransferOptions.aspirate.__doc__ = """
353
+ Option to customize aspirate rate. See
354
+ :py:class:`.AspirateOpts`.
355
+ """
356
+
357
+ TransferOptions.dispense.__doc__ = """
358
+ Option to customize dispense rate. See
359
+ :py:class:`.DispenseOpts`.
360
+ """
361
+
362
+
363
+ class TransferPlan:
364
+ """Calculate and carry state for an arbitrary transfer
365
+
366
+ This class encapsulates the logic around planning an M:N transfer.
367
+
368
+ It handles calculations based on pipette channels, tip management, and all
369
+ the various little commands that can be involved in a transfer. It can be
370
+ iterated to resolve methods to call to execute the plan.
371
+ """
372
+
373
+ def __init__(
374
+ self,
375
+ volume: Union[float, Sequence[float]],
376
+ srcs: AdvancedLiquidHandling,
377
+ dsts: AdvancedLiquidHandling,
378
+ # todo(mm, 2021-03-10):
379
+ # Refactor to not need an InstrumentContext, so we can more
380
+ # easily test this class's logic on its own.
381
+ instr: "InstrumentContext",
382
+ max_volume: float,
383
+ api_version: APIVersion,
384
+ mode: str,
385
+ options: Optional[TransferOptions] = None,
386
+ ) -> None:
387
+ """Build the transfer plan.
388
+
389
+ This method initializes the object and does the work of preparing the
390
+ transfer plan. Its arguments are as those of
391
+ :py:meth:`.InstrumentContext.transfer`.
392
+ """
393
+ self._instr = instr
394
+ self._api_version = api_version
395
+ # Convert sources & dests into proper format
396
+ # CASES:
397
+ # i. if using multi-channel pipette,
398
+ # and the source or target is a row/column of Wells (i.e list of Wells)
399
+ # then avoid iterating through its Wells.
400
+ # ii. if using single channel pipettes, flatten a multi-dimensional
401
+ # list of Wells into a 1 dimensional list of Wells
402
+ pipette_configuration_type = types.NozzleConfigurationType.FULL
403
+ normalized_sources: List[Union[Well, types.Location]]
404
+ normalized_dests: List[Union[Well, types.Location]]
405
+ if self._api_version >= _PARTIAL_TIP_SUPPORT_ADDED:
406
+ pipette_configuration_type = (
407
+ self._instr._core.get_nozzle_map().configuration
408
+ )
409
+ if (
410
+ self._instr.channels > 1
411
+ and pipette_configuration_type == types.NozzleConfigurationType.FULL
412
+ ):
413
+ normalized_sources, normalized_dests = self._multichannel_transfer(
414
+ srcs, dsts
415
+ )
416
+ else:
417
+ if isinstance(srcs, List):
418
+ if isinstance(srcs[0], List):
419
+ # Source is a List[List[Well]]
420
+ normalized_sources = [
421
+ well for well_list in srcs for well in well_list
422
+ ]
423
+ else:
424
+ normalized_sources = srcs
425
+ elif isinstance(srcs, Well) or isinstance(srcs, types.Location):
426
+ normalized_sources = [srcs]
427
+ if isinstance(dsts, List):
428
+ if isinstance(dsts[0], List):
429
+ # Dest is a List[List[Well]]
430
+ normalized_dests = [
431
+ well for well_list in dsts for well in well_list
432
+ ]
433
+ else:
434
+ normalized_dests = dsts
435
+ elif isinstance(dsts, Well) or isinstance(dsts, types.Location):
436
+ normalized_dests = [dsts]
437
+
438
+ total_xfers = max(len(normalized_sources), len(normalized_dests))
439
+
440
+ self._volumes = self._create_volume_list(volume, total_xfers)
441
+ self._sources = normalized_sources
442
+ self._dests = normalized_dests
443
+ self._options = options or TransferOptions()
444
+ self._strategy = self._options.transfer
445
+ self._tip_opts = self._options.pick_up_tip
446
+ self._blow_opts = self._options.blow_out
447
+ self._touch_tip_opts = self._options.touch_tip
448
+ self._mix_before_opts = self._options.mix.mix_before
449
+ self._mix_after_opts = self._options.mix.mix_after
450
+ self._max_volume = max_volume
451
+
452
+ self._mode = TransferMode[mode.upper()]
453
+
454
+ def __iter__(self) -> Iterator[TransferStep]:
455
+ if self._strategy.new_tip == types.TransferTipPolicy.ONCE:
456
+ yield self._format_dict("pick_up_tip", kwargs=self._tip_opts)
457
+ yield from {
458
+ TransferMode.CONSOLIDATE: self._plan_consolidate,
459
+ TransferMode.DISTRIBUTE: self._plan_distribute,
460
+ TransferMode.TRANSFER: self._plan_transfer,
461
+ }[self._mode]()
462
+ if self._strategy.new_tip == types.TransferTipPolicy.ONCE:
463
+ if self._strategy.drop_tip_strategy == DropTipStrategy.RETURN:
464
+ yield self._format_dict("return_tip")
465
+ else:
466
+ yield self._format_dict("drop_tip")
467
+
468
+ def _plan_transfer(self) -> Generator[TransferStep, None, None]:
469
+ """
470
+ * **Source/ Dest:** Multiple sources to multiple destinations.
471
+ Src & dest should be equal length
472
+
473
+ * **Volume:** Single volume or List of volumes is acceptable. This list
474
+ should be same length as sources/destinations
475
+
476
+ * **Behavior with transfer options:**
477
+
478
+ - New_tip: can be either NEVER or ONCE or ALWAYS
479
+ - Air_gap: if specified, will be performed after every aspirate
480
+ - Blow_out: can be performed after each dispense (after mix, before
481
+ touch_tip) at the location specified. If there is
482
+ liquid present in the tip (as in the case of nonzero
483
+ disposal volume), blow_out will be performed at either
484
+ user-defined location or (default) trash.
485
+ If no liquid is supposed to be present in the tip after
486
+ dispense, blow_out will be performed at dispense well
487
+ location (if blow out strategy is DEST)
488
+ - Touch_tip: can be performed after each aspirate and/or after
489
+ each dispense
490
+ - Mix: can be performed before aspirate and/or after dispense
491
+ if there is no disposal volume (i.e. can be performed
492
+ only when the tip is supposed to be empty)
493
+
494
+ Considering all options, the sequence of actions is:
495
+ *New Tip -> Mix -> Aspirate (with disposal volume) -> Air gap ->
496
+ -> Touch tip -> Dispense air gap -> Dispense -> Mix if empty ->
497
+ -> Blow out -> Touch tip -> Drop tip*
498
+ """
499
+ # reform source target lists
500
+ sources, dests = self._extend_source_target_lists(self._sources, self._dests)
501
+ tx_commons.check_valid_volume_parameters(
502
+ disposal_volume=self._strategy.disposal_volume,
503
+ air_gap=self._strategy.air_gap,
504
+ max_volume=self._instr.max_volume,
505
+ )
506
+ plan_iter = tx_commons.expand_for_volume_constraints(
507
+ self._volumes,
508
+ zip(sources, dests),
509
+ self._instr.max_volume
510
+ - self._strategy.disposal_volume
511
+ - self._strategy.air_gap,
512
+ )
513
+ for step_vol, (src, dest) in plan_iter:
514
+ if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS:
515
+ yield self._format_dict("pick_up_tip", kwargs=self._tip_opts)
516
+ max_vol = (
517
+ self._max_volume
518
+ - self._strategy.disposal_volume
519
+ - self._strategy.air_gap
520
+ )
521
+ xferred_vol = 0.0
522
+ while xferred_vol < step_vol:
523
+ # TODO: account for unequal length sources, dests
524
+ # TODO: ensure last transfer is > min_vol
525
+ vol = min(max_vol, step_vol - xferred_vol)
526
+ yield from self._aspirate_actions(vol, src)
527
+ yield from self._dispense_actions(vol=vol, dest=dest, src=src)
528
+ xferred_vol += vol
529
+ yield from self._new_tip_action()
530
+
531
+ @staticmethod
532
+ def _extend_source_target_lists(
533
+ sources: List[Union[Well, types.Location]],
534
+ targets: List[Union[Well, types.Location]],
535
+ ) -> Tuple[List[Union[Well, types.Location]], List[Union[Well, types.Location]]]:
536
+ """Extend source or target list to match the length of the other"""
537
+ if len(sources) < len(targets):
538
+ if len(targets) % len(sources) != 0:
539
+ raise ValueError("Source and destination lists must be divisible")
540
+ sources = [
541
+ source
542
+ for source in sources
543
+ for i in range(int(len(targets) / len(sources)))
544
+ ]
545
+ elif len(sources) > len(targets):
546
+ if len(sources) % len(targets) != 0:
547
+ raise ValueError("Source and destination lists must be divisible")
548
+ targets = [
549
+ target
550
+ for target in targets
551
+ for i in range(int(len(sources) / len(targets)))
552
+ ]
553
+ return sources, targets
554
+
555
+ def _plan_distribute(self) -> Generator[TransferStep, None, None]:
556
+ """
557
+ * **Source/ Dest:** One source to many destinations
558
+ * **Volume:** Single volume or List of volumes is acceptable. This list
559
+ should be same length as destinations
560
+ * **Behavior with transfer options:**
561
+
562
+ - New_tip: can be either NEVER or ONCE
563
+ (ALWAYS will fallback to ONCE)
564
+ - Air_gap: if specified, will be performed after every aspirate and
565
+ also in-between dispenses (to keep air gap while moving
566
+ between wells)
567
+ - Blow_out: can be performed at the end of distribute (after mix,
568
+ before touch_tip) at the location specified. If there
569
+ is liquid present in the tip, blow_out will be
570
+ performed at either user-defined location or (default)
571
+ trash. If no liquid is supposed to be present in the
572
+ tip at the end of distribute, blow_out will be
573
+ performed at the last well the liquid was dispensed to
574
+ (if strategy is DEST)
575
+ - Touch_tip: can be performed after each aspirate and/or after
576
+ every dispense
577
+ - Mix: can be performed before aspirate and/or after the last
578
+ dispense if there is no disposal volume (i.e. can be
579
+ performed only when the tip is supposed to be empty)
580
+
581
+ Considering all options, the sequence of actions is:
582
+
583
+ 1. Going from source to dest1:
584
+ *New Tip -> Mix -> Aspirate (with disposal volume) -> Air gap ->
585
+ -> Touch tip -> Dispense air gap -> Dispense -> Mix if empty ->
586
+ -> Blow out -> Touch tip -> Drop tip*
587
+ 2. Going from destn to destn+1:
588
+ *.. Dispense air gap -> Dispense -> Touch tip -> Air gap ->
589
+ .. Dispense air gap -> ...*
590
+
591
+ """
592
+
593
+ tx_commons.check_valid_volume_parameters(
594
+ disposal_volume=self._strategy.disposal_volume,
595
+ air_gap=self._strategy.air_gap,
596
+ max_volume=self._instr.max_volume,
597
+ )
598
+
599
+ # TODO: decide whether default disposal vol for distribute should be
600
+ # pipette min_vol or should we leave it to being 0 by default and
601
+ # recommend users to specify a disposal vol when using distribute.
602
+ # First method keeps distribute consistent with current behavior while
603
+ # the other maintains consistency in default behaviors of all functions
604
+ plan_iter = tx_commons.expand_for_volume_constraints(
605
+ self._volumes,
606
+ self._dests,
607
+ # todo(mm, 2021-03-09): Is it right for this to be
608
+ # _instr_.max_volume? Does/should this take the tip maximum volume
609
+ # into account?
610
+ self._instr.max_volume
611
+ - self._strategy.disposal_volume
612
+ - self._strategy.air_gap,
613
+ )
614
+
615
+ done = False
616
+ current_xfer = next(plan_iter)
617
+ if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS:
618
+ yield self._format_dict("pick_up_tip", kwargs=self._tip_opts)
619
+ while not done:
620
+ asp_grouped: List[Tuple[float, Well | types.Location]] = []
621
+ try:
622
+ while (
623
+ sum(a[0] for a in asp_grouped)
624
+ + self._strategy.disposal_volume
625
+ + self._strategy.air_gap
626
+ + current_xfer[0]
627
+ ) <= self._max_volume:
628
+ append_xfer = self._check_volume_not_zero(
629
+ self._api_version, current_xfer[0]
630
+ )
631
+ if append_xfer:
632
+ asp_grouped.append(current_xfer)
633
+ current_xfer = next(plan_iter)
634
+ except StopIteration:
635
+ done = True
636
+ if not asp_grouped:
637
+ break
638
+
639
+ yield from self._aspirate_actions(
640
+ sum(a[0] for a in asp_grouped) + self._strategy.disposal_volume,
641
+ self._sources[0],
642
+ )
643
+ for step in asp_grouped:
644
+
645
+ yield from self._dispense_actions(
646
+ vol=step[0],
647
+ src=self._sources[0],
648
+ dest=step[1],
649
+ is_disp_next=step is not asp_grouped[-1],
650
+ )
651
+ yield from self._new_tip_action()
652
+
653
+ def _plan_consolidate(self) -> Generator[TransferStep, None, None]:
654
+ """
655
+ * **Source/ Dest:** Many sources to one destination
656
+ * **Volume:** Single volume or List of volumes is acceptable. This list
657
+ should be same length as sources
658
+ * **Behavior with transfer options:**
659
+
660
+ - New_tip: can be either NEVER or ONCE
661
+ (ALWAYS will fallback to ONCE)
662
+ - Air_gap: if specified, will be performed after every aspirate
663
+ so that the aspirated liquids do not mix inside the tip.
664
+ The air gap will be dispensed while dispensing the
665
+ liquid into the destination well.
666
+ - Blow_out: can be performed after a dispense (after mix,
667
+ before touch_tip) at the location specified. If there
668
+ is liquid present in the tip (which shouldn't happen
669
+ since consolidate doesn't take a disposal vol, yet),
670
+ blow_out will be performed at either user-defined
671
+ location or (default) trash.
672
+ If no liquid is supposed to be present in the tip after
673
+ dispense, blow_out will be performed at dispense well
674
+ loc (if blow out strategy is DEST)
675
+ - Touch_tip: can be performed after each aspirate and/or after
676
+ dispense
677
+ - Mix: can be performed before the first aspirate and/or after
678
+ dispense if there is no disposal volume (i.e. can be
679
+ performed only when the tip is supposed to be empty)
680
+
681
+ Considering all options, the sequence of actions is:
682
+ 1. Going from source to dest1:
683
+ *New Tip -> Mix -> Aspirate (with disposal volume?) -> Air gap
684
+ -> Touch tip -> Dispense air gap -> Dispense -> Mix if empty ->
685
+ -> Blow out -> Touch tip -> Drop tip*
686
+ 2. Going from source(n) to source(n+1):
687
+ *.. Aspirate -> Air gap -> Touch tip ->..
688
+ .. Aspirate -> .....*
689
+ """
690
+ # TODO: verify if _check_valid_volume_parameters should be re-enabled here
691
+ # self._check_valid_volume_parameters(
692
+ # disposal_volume=self._strategy.disposal_volume,
693
+ # air_gap=self._strategy.air_gap,
694
+ # max_volume=self._instr.max_volume,
695
+ # )
696
+ plan_iter = tx_commons.expand_for_volume_constraints(
697
+ # todo(mm, 2021-03-09): Is it right to use _instr.max_volume here?
698
+ # Why don't we account for tip max volume, disposal volume, or air
699
+ # gap?
700
+ self._volumes,
701
+ self._sources,
702
+ self._instr.max_volume,
703
+ )
704
+ current_xfer = next(plan_iter)
705
+ if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS:
706
+ yield self._format_dict("pick_up_tip", kwargs=self._tip_opts)
707
+ done = False
708
+ while not done:
709
+ asp_grouped: List[Tuple[float, Union[Well, types.Location]]] = []
710
+ try:
711
+ while (
712
+ sum([a[0] for a in asp_grouped])
713
+ + self._strategy.disposal_volume
714
+ + self._strategy.air_gap * len(asp_grouped)
715
+ + current_xfer[0]
716
+ ) <= self._max_volume:
717
+ append_xfer = self._check_volume_not_zero(
718
+ self._api_version, current_xfer[0]
719
+ )
720
+ if append_xfer:
721
+ asp_grouped.append(current_xfer)
722
+ current_xfer = next(plan_iter)
723
+ except StopIteration:
724
+ done = True
725
+ if not asp_grouped:
726
+ break
727
+ # Q: What accounts as disposal volume in a consolidate action?
728
+ # yield self._format_dict('aspirate',
729
+ # self._strategy.disposal_volume, loc)
730
+ for step in asp_grouped:
731
+ yield from self._aspirate_actions(step[0], step[1])
732
+ yield from self._dispense_actions(
733
+ vol=sum([a[0] + self._strategy.air_gap for a in asp_grouped])
734
+ - self._strategy.air_gap,
735
+ src=None,
736
+ dest=self._dests[0],
737
+ )
738
+ yield from self._new_tip_action()
739
+
740
+ def _aspirate_actions(
741
+ self, vol: float, loc: Union[Well, types.Location]
742
+ ) -> Generator[TransferStep, None, None]:
743
+ yield from self._before_aspirate(loc)
744
+ yield self._format_dict("aspirate", [vol, loc, self._options.aspirate.rate])
745
+ yield from self._after_aspirate()
746
+
747
+ def _dispense_actions(
748
+ self,
749
+ vol: float,
750
+ dest: Union[Well, types.Location],
751
+ src: Optional[Union[Well, types.Location]] = None,
752
+ is_disp_next: bool = False,
753
+ ) -> Generator[TransferStep, None, None]:
754
+ if self._strategy.air_gap:
755
+ vol += self._strategy.air_gap
756
+ yield self._format_dict("dispense", [vol, dest, self._options.dispense.rate])
757
+ yield from self._after_dispense(dest=dest, src=src, is_disp_next=is_disp_next)
758
+
759
+ def _before_aspirate(
760
+ self, loc: Union[Well, types.Location]
761
+ ) -> Generator[TransferStep, None, None]:
762
+ if (
763
+ self._strategy.mix_strategy == MixStrategy.BEFORE
764
+ or self._strategy.mix_strategy == MixStrategy.BOTH
765
+ ):
766
+ if self._instr.current_volume == 0:
767
+ mix_before_opts = self._mix_before_opts._asdict()
768
+ mix_before_opts["location"] = loc
769
+ yield self._format_dict("mix", kwargs=mix_before_opts)
770
+
771
+ def _after_aspirate(self) -> Generator[TransferStep, None, None]:
772
+ if self._strategy.air_gap:
773
+ yield self._format_dict("air_gap", [self._strategy.air_gap])
774
+ if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS:
775
+ yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts)
776
+
777
+ def _after_dispense_trash(self) -> Generator[TransferStep, None, None]:
778
+ if isinstance(self._instr.trash_container, Labware):
779
+ yield self._format_dict(
780
+ "blow_out", [self._instr.trash_container.wells()[0]]
781
+ )
782
+ else:
783
+ yield self._format_dict("blow_out", [self._instr.trash_container])
784
+
785
+ def _after_dispense_helper(self) -> Generator[TransferStep, None, None]:
786
+ # Used by distribute
787
+ if self._strategy.air_gap:
788
+ yield self._format_dict("air_gap", [self._strategy.air_gap])
789
+ if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS:
790
+ yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts)
791
+
792
+ def _after_dispense(
793
+ self,
794
+ dest: Union[Well, types.Location],
795
+ src: Optional[Union[types.Location, Well]],
796
+ is_disp_next: bool = False,
797
+ ) -> Generator[TransferStep, None, None]:
798
+ # This sequence of actions is subject to change
799
+ if not is_disp_next:
800
+ # If the next command is an aspirate, we are switching
801
+ # between aspirate and dispense.
802
+ if self._instr.current_volume == 0:
803
+ # If we're empty, then this is when after mixes come into play
804
+ if (
805
+ self._strategy.mix_strategy == MixStrategy.AFTER
806
+ or self._strategy.mix_strategy == MixStrategy.BOTH
807
+ ):
808
+ mix_after_opts = self._mix_after_opts._asdict()
809
+ mix_after_opts["location"] = dest
810
+ yield self._format_dict("mix", kwargs=mix_after_opts)
811
+ if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS:
812
+ yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts)
813
+
814
+ if self._strategy.blow_out_strategy == BlowOutStrategy.SOURCE:
815
+ yield self._format_dict("blow_out", [src])
816
+ elif self._strategy.blow_out_strategy == BlowOutStrategy.DEST:
817
+ yield self._format_dict("blow_out", [dest])
818
+ elif self._strategy.blow_out_strategy == BlowOutStrategy.CUSTOM_LOCATION:
819
+ yield self._format_dict("blow_out", kwargs=self._blow_opts)
820
+ elif (
821
+ self._strategy.blow_out_strategy == BlowOutStrategy.TRASH
822
+ or self._strategy.disposal_volume
823
+ ):
824
+ yield from self._after_dispense_trash()
825
+ else:
826
+ yield from self._after_dispense_helper()
827
+
828
+ def _new_tip_action(self) -> Generator[TransferStep, None, None]:
829
+ if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS:
830
+ if self._strategy.drop_tip_strategy == DropTipStrategy.RETURN:
831
+ yield self._format_dict("return_tip")
832
+ else:
833
+ yield self._format_dict("drop_tip")
834
+
835
+ def _format_dict(
836
+ self,
837
+ method: str,
838
+ args: Optional[List[Any]] = None,
839
+ kwargs: Optional[Union[Dict[Any, Any], FormatDictArgs]] = None,
840
+ ) -> TransferStep:
841
+ if kwargs:
842
+ if isinstance(kwargs, Dict):
843
+ params = {key: val for key, val in kwargs.items() if val}
844
+ else:
845
+ params = {key: val for key, val in kwargs._asdict().items() if val}
846
+ else:
847
+ params = {}
848
+ if not args:
849
+ args = []
850
+ return {"method": method, "args": args, "kwargs": params}
851
+
852
+ def _create_volume_list(
853
+ self, volume: Union[Union[float, int], Sequence[float]], total_xfers: int
854
+ ) -> List[float]:
855
+ if isinstance(volume, (float, int)):
856
+ return [float(volume)] * total_xfers
857
+ elif isinstance(volume, tuple):
858
+ return self._create_volume_gradient(
859
+ volume[0], volume[-1], total_xfers, self._strategy.gradient_function
860
+ )
861
+ else:
862
+ if not isinstance(volume, List):
863
+ raise TypeError(
864
+ "Volume expected as a number or List or"
865
+ " tuple but got {}".format(volume)
866
+ )
867
+ elif not len(volume) == total_xfers:
868
+ raise RuntimeError(
869
+ "List of volumes should be equal to number " "of transfers"
870
+ )
871
+ return volume
872
+
873
+ @staticmethod
874
+ def _create_volume_gradient(
875
+ min_v: float,
876
+ max_v: float,
877
+ total: int,
878
+ gradient: Optional[Callable[[float], float]] = None,
879
+ ) -> List[float]:
880
+
881
+ diff_vol = max_v - min_v
882
+
883
+ def _map_volume(i: int) -> float:
884
+ nonlocal diff_vol, total
885
+ rel_x = i / (total - 1)
886
+ rel_y = gradient(rel_x) if gradient else rel_x
887
+ return (rel_y * diff_vol) + min_v
888
+
889
+ return [_map_volume(i) for i in range(total)]
890
+
891
+ def _check_valid_well_list(
892
+ self, well_list: List[Any], id: str, old_well_list: List[Any]
893
+ ) -> None:
894
+ if self._api_version >= APIVersion(2, 2) and len(well_list) < 1:
895
+ raise RuntimeError(
896
+ f"Invalid {id} for multichannel transfer: {old_well_list}"
897
+ )
898
+
899
+ @staticmethod
900
+ def _check_volume_not_zero(api_version: APIVersion, volume: float) -> bool:
901
+ # We should only be adding volumes to transfer plans if it is
902
+ # greater than zero to prevent extraneous robot movements.
903
+ if api_version < APIVersion(2, 8):
904
+ return True
905
+ elif volume > 0:
906
+ return True
907
+ return False
908
+
909
+ def _multichannel_transfer(
910
+ self, s: AdvancedLiquidHandling, d: AdvancedLiquidHandling
911
+ ) -> Tuple[List[Union[Well, types.Location]], List[Union[Well, types.Location]]]:
912
+ # TODO: add a check for container being multi-channel compatible?
913
+ # Helper function for multi-channel use-case
914
+ assert (
915
+ isinstance(s, Well)
916
+ or isinstance(s, types.Location)
917
+ or (isinstance(s, List) and isinstance(s[0], Well))
918
+ or (isinstance(s, List) and isinstance(s[0], List))
919
+ or (isinstance(s, List) and isinstance(s[0], types.Location))
920
+ ), "Source should be a Well or List[Well] but is {}".format(s)
921
+ assert (
922
+ isinstance(d, Well)
923
+ or isinstance(d, types.Location)
924
+ or (isinstance(d, List) and isinstance(d[0], Well))
925
+ or (isinstance(d, List) and isinstance(d[0], List))
926
+ or (isinstance(d, List) and isinstance(d[0], types.Location))
927
+ ), "Target should be a Well or List[Well] but is {}".format(d)
928
+
929
+ # TODO: Account for cases where a src/dest list has a non-first-row
930
+ # well (eg, 'B1') and would expect the robot/pipette to
931
+ # understand that it is referring to the whole first column
932
+ if isinstance(s, List) and isinstance(s[0], List):
933
+ # s is a List[List]]; flatten to 1D list
934
+ s = [well for list_elem in s for well in list_elem]
935
+ elif isinstance(s, Well) or isinstance(s, types.Location):
936
+ s = [s]
937
+ new_src = []
938
+ for well in s:
939
+ if self._is_valid_row(well):
940
+ new_src.append(well)
941
+ self._check_valid_well_list(new_src, "source", s)
942
+
943
+ if isinstance(d, List) and isinstance(d[0], List):
944
+ # s is a List[List]]; flatten to 1D list
945
+ d = [well for list_elem in d for well in list_elem]
946
+ elif isinstance(d, Well) or isinstance(d, types.Location):
947
+ d = [d]
948
+ new_dst = []
949
+ for well in d:
950
+ if self._is_valid_row(well):
951
+ new_dst.append(well)
952
+ self._check_valid_well_list(new_dst, "target", d)
953
+ return new_src, new_dst
954
+
955
+ def _is_valid_row(self, well: Union[Well, types.Location]) -> bool:
956
+ if isinstance(well, types.Location):
957
+ test_well = well.labware.as_well()
958
+ else:
959
+ test_well = well
960
+
961
+ if self._api_version < APIVersion(2, 2):
962
+ return test_well in test_well.parent.rows()[0]
963
+ else:
964
+ # Allow the first 2 rows to be accessible to 384-well plates;
965
+ # otherwise, only the first row is accessible
966
+ if test_well.parent.parameters["format"] == "384Standard":
967
+ valid_wells = [
968
+ well for row in test_well.parent.rows()[:2] for well in row
969
+ ]
970
+ return test_well in valid_wells
971
+ else:
972
+ return test_well in test_well.parent.rows()[0]