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,905 @@
1
+ """Basic pipette data state and store."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import dataclasses
6
+ from logging import getLogger
7
+ from typing import (
8
+ Dict,
9
+ List,
10
+ Mapping,
11
+ Optional,
12
+ Tuple,
13
+ cast,
14
+ )
15
+
16
+ from typing_extensions import assert_never
17
+
18
+ from opentrons_shared_data.pipette import pipette_definition
19
+ from opentrons_shared_data.pipette.ul_per_mm import calculate_ul_per_mm
20
+ from opentrons_shared_data.pipette.types import UlPerMmAction
21
+
22
+ from opentrons.config.defaults_ot2 import Z_RETRACT_DISTANCE
23
+ from opentrons.hardware_control.dev_types import PipetteDict
24
+ from opentrons.hardware_control import CriticalPoint
25
+ from opentrons.hardware_control.nozzle_manager import (
26
+ NozzleMap,
27
+ )
28
+ from opentrons.types import MountType, Mount as HwMount, Point, NozzleConfigurationType
29
+
30
+ from . import update_types, fluid_stack
31
+ from .. import errors
32
+ from ..types import (
33
+ LoadedPipette,
34
+ MotorAxis,
35
+ FlowRates,
36
+ DeckPoint,
37
+ CurrentWell,
38
+ CurrentAddressableArea,
39
+ CurrentPipetteLocation,
40
+ TipGeometry,
41
+ LabwareWellId,
42
+ )
43
+ from ..actions import (
44
+ Action,
45
+ SetPipetteMovementSpeedAction,
46
+ get_state_updates,
47
+ )
48
+ from ._abstract_store import HasState, HandlesActions
49
+
50
+ LOG = getLogger(__name__)
51
+
52
+
53
+ @dataclasses.dataclass(frozen=True)
54
+ class HardwarePipette:
55
+ """Hardware pipette data."""
56
+
57
+ mount: HwMount
58
+ config: PipetteDict
59
+
60
+
61
+ @dataclasses.dataclass(frozen=True)
62
+ class CurrentDeckPoint:
63
+ """The latest deck point and mount the robot has accessed."""
64
+
65
+ mount: Optional[MountType]
66
+ deck_point: Optional[DeckPoint]
67
+
68
+
69
+ @dataclasses.dataclass(frozen=True)
70
+ class BoundingNozzlesOffsets:
71
+ """Offsets of the bounding nozzles of the pipette."""
72
+
73
+ back_left_offset: Point
74
+ front_right_offset: Point
75
+
76
+
77
+ @dataclasses.dataclass(frozen=True)
78
+ class PipetteBoundingBoxOffsets:
79
+ """Offsets of the corners of the pipette's bounding box."""
80
+
81
+ back_left_corner: Point
82
+ front_right_corner: Point
83
+ back_right_corner: Point
84
+ front_left_corner: Point
85
+
86
+
87
+ @dataclasses.dataclass(frozen=True)
88
+ class StaticPipetteConfig:
89
+ """Static config for a pipette."""
90
+
91
+ model: str
92
+ serial_number: str
93
+ display_name: str
94
+ min_volume: float
95
+ max_volume: float
96
+ channels: int
97
+ tip_configuration_lookup_table: Dict[
98
+ float, pipette_definition.SupportedTipsDefinition
99
+ ]
100
+ nominal_tip_overlap: Dict[str, float]
101
+ home_position: float
102
+ nozzle_offset_z: float
103
+ pipette_bounding_box_offsets: PipetteBoundingBoxOffsets
104
+ bounding_nozzle_offsets: BoundingNozzlesOffsets
105
+ default_nozzle_map: NozzleMap # todo(mm, 2024-10-14): unused, remove?
106
+ lld_settings: Optional[Dict[str, Dict[str, float]]]
107
+ plunger_positions: Dict[str, float]
108
+ shaft_ul_per_mm: float
109
+ available_sensors: pipette_definition.AvailableSensorDefinition
110
+
111
+
112
+ @dataclasses.dataclass
113
+ class PipetteState:
114
+ """Basic pipette data state and getter methods."""
115
+
116
+ # todo(mm, 2024-10-14): It's getting difficult to ensure that all of these
117
+ # attributes are populated at the appropriate times. Refactor to a
118
+ # single dict-of-many-things instead of many dicts-of-single-things.
119
+ pipettes_by_id: Dict[str, LoadedPipette]
120
+ pipette_contents_by_id: Dict[str, Optional[fluid_stack.FluidStack]]
121
+ current_location: Optional[CurrentPipetteLocation]
122
+ current_deck_point: CurrentDeckPoint
123
+ attached_tip_by_id: Dict[str, Optional[TipGeometry]]
124
+ movement_speed_by_id: Dict[str, Optional[float]]
125
+ static_config_by_id: Dict[str, StaticPipetteConfig]
126
+ flow_rates_by_id: Dict[str, FlowRates]
127
+ nozzle_configuration_by_id: Dict[str, NozzleMap]
128
+ liquid_presence_detection_by_id: Dict[str, bool]
129
+ ready_to_aspirate_by_id: Dict[str, bool]
130
+ has_clean_tips_by_id: Dict[str, bool]
131
+ tip_source_by_id: Dict[str, Optional[LabwareWellId]]
132
+
133
+
134
+ class PipetteStore(HasState[PipetteState], HandlesActions):
135
+ """Pipette state container."""
136
+
137
+ _state: PipetteState
138
+
139
+ def __init__(self) -> None:
140
+ """Initialize a PipetteStore and its state."""
141
+ self._state = PipetteState(
142
+ pipettes_by_id={},
143
+ pipette_contents_by_id={},
144
+ attached_tip_by_id={},
145
+ current_location=None,
146
+ current_deck_point=CurrentDeckPoint(mount=None, deck_point=None),
147
+ movement_speed_by_id={},
148
+ static_config_by_id={},
149
+ flow_rates_by_id={},
150
+ nozzle_configuration_by_id={},
151
+ liquid_presence_detection_by_id={},
152
+ ready_to_aspirate_by_id={},
153
+ has_clean_tips_by_id={},
154
+ tip_source_by_id={},
155
+ )
156
+
157
+ def handle_action(self, action: Action) -> None:
158
+ """Modify state in reaction to an action."""
159
+ for state_update in get_state_updates(action):
160
+ self._set_load_pipette(state_update)
161
+ self._update_current_location(state_update)
162
+ self._update_pipette_config(state_update)
163
+ self._update_pipette_nozzle_map(state_update)
164
+ self._update_tip_state(state_update)
165
+ self._update_volumes(state_update)
166
+ self._update_ready_for_aspirate(state_update)
167
+
168
+ if isinstance(action, SetPipetteMovementSpeedAction):
169
+ self._state.movement_speed_by_id[action.pipette_id] = action.speed
170
+
171
+ def _set_load_pipette(self, state_update: update_types.StateUpdate) -> None:
172
+ if state_update.loaded_pipette != update_types.NO_CHANGE:
173
+ pipette_id = state_update.loaded_pipette.pipette_id
174
+
175
+ self._state.pipettes_by_id[pipette_id] = LoadedPipette(
176
+ id=pipette_id,
177
+ pipetteName=state_update.loaded_pipette.pipette_name,
178
+ mount=state_update.loaded_pipette.mount,
179
+ )
180
+ self._state.liquid_presence_detection_by_id[pipette_id] = (
181
+ state_update.loaded_pipette.liquid_presence_detection or False
182
+ )
183
+ self._state.movement_speed_by_id[pipette_id] = None
184
+ self._state.attached_tip_by_id[pipette_id] = None
185
+ self._state.ready_to_aspirate_by_id[pipette_id] = False
186
+ self._state.tip_source_by_id[pipette_id] = None
187
+
188
+ def _update_tip_state(self, state_update: update_types.StateUpdate) -> None:
189
+ if state_update.pipette_tip_state != update_types.NO_CHANGE:
190
+ pipette_id = state_update.pipette_tip_state.pipette_id
191
+ if state_update.pipette_tip_state.tip_geometry:
192
+ attached_tip = state_update.pipette_tip_state.tip_geometry
193
+
194
+ self._state.attached_tip_by_id[pipette_id] = attached_tip
195
+ self._state.tip_source_by_id[
196
+ pipette_id
197
+ ] = state_update.pipette_tip_state.tip_source
198
+
199
+ static_config = self._state.static_config_by_id.get(pipette_id)
200
+ if static_config:
201
+ try:
202
+ tip_configuration = (
203
+ static_config.tip_configuration_lookup_table[
204
+ attached_tip.volume
205
+ ]
206
+ )
207
+ except KeyError:
208
+ # TODO(seth,9/11/2023): this is a bad way of doing defaults but better than max volume.
209
+ # we used to look up a default tip config via the pipette max volume, but if that isn't
210
+ # tip volume (as it isn't when we're in low-volume mode) then that lookup fails. Using
211
+ # the first entry in the table is ok I guess but we really need to generally rethink how
212
+ # we identify tip classes - looking things up by volume is not enough.
213
+ tip_configuration = list(
214
+ static_config.tip_configuration_lookup_table.values()
215
+ )[0]
216
+ self._state.flow_rates_by_id[pipette_id] = FlowRates(
217
+ default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level,
218
+ default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level,
219
+ default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level,
220
+ )
221
+
222
+ else:
223
+ pipette_id = state_update.pipette_tip_state.pipette_id
224
+ self._state.attached_tip_by_id[pipette_id] = None
225
+ self._state.has_clean_tips_by_id[pipette_id] = False
226
+ self._state.tip_source_by_id[pipette_id] = None
227
+
228
+ static_config = self._state.static_config_by_id.get(pipette_id)
229
+ if static_config:
230
+ # TODO(seth,9/11/2023): bad way to do defaulting, see above.
231
+ tip_configuration = list(
232
+ static_config.tip_configuration_lookup_table.values()
233
+ )[0]
234
+ self._state.flow_rates_by_id[pipette_id] = FlowRates(
235
+ default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level,
236
+ default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level,
237
+ default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level,
238
+ )
239
+
240
+ def _update_current_location(self, state_update: update_types.StateUpdate) -> None:
241
+ location_update = state_update.pipette_location
242
+
243
+ if location_update is update_types.NO_CHANGE:
244
+ pass
245
+ elif location_update is update_types.CLEAR:
246
+ self._state.current_location = None
247
+ self._state.current_deck_point = CurrentDeckPoint(
248
+ mount=None, deck_point=None
249
+ )
250
+ else:
251
+ new_logical_location = location_update.new_location
252
+ new_deck_point = location_update.new_deck_point
253
+ match new_logical_location:
254
+ case LabwareWellId(labware_id=labware_id, well_name=well_name):
255
+ self._state.current_location = CurrentWell(
256
+ pipette_id=location_update.pipette_id,
257
+ labware_id=labware_id,
258
+ well_name=well_name,
259
+ )
260
+ case update_types.AddressableArea(
261
+ addressable_area_name=addressable_area_name
262
+ ):
263
+ self._state.current_location = CurrentAddressableArea(
264
+ pipette_id=location_update.pipette_id,
265
+ addressable_area_name=addressable_area_name,
266
+ )
267
+ case None:
268
+ self._state.current_location = None
269
+ case update_types.NO_CHANGE:
270
+ pass
271
+ if new_deck_point is not update_types.NO_CHANGE:
272
+ loaded_pipette = self._state.pipettes_by_id[location_update.pipette_id]
273
+ self._state.current_deck_point = CurrentDeckPoint(
274
+ mount=loaded_pipette.mount, deck_point=new_deck_point
275
+ )
276
+
277
+ def _update_pipette_config(self, state_update: update_types.StateUpdate) -> None:
278
+ if state_update.pipette_config != update_types.NO_CHANGE:
279
+ config = state_update.pipette_config.config
280
+ self._state.static_config_by_id[
281
+ state_update.pipette_config.pipette_id
282
+ ] = StaticPipetteConfig(
283
+ serial_number=state_update.pipette_config.serial_number,
284
+ model=config.model,
285
+ display_name=config.display_name,
286
+ min_volume=config.min_volume,
287
+ max_volume=config.max_volume,
288
+ channels=config.channels,
289
+ tip_configuration_lookup_table=config.tip_configuration_lookup_table,
290
+ nominal_tip_overlap=config.nominal_tip_overlap,
291
+ home_position=config.home_position,
292
+ nozzle_offset_z=config.nozzle_offset_z,
293
+ pipette_bounding_box_offsets=PipetteBoundingBoxOffsets(
294
+ back_left_corner=config.back_left_corner_offset,
295
+ front_right_corner=config.front_right_corner_offset,
296
+ back_right_corner=Point(
297
+ config.front_right_corner_offset.x,
298
+ config.back_left_corner_offset.y,
299
+ config.back_left_corner_offset.z,
300
+ ),
301
+ front_left_corner=Point(
302
+ config.back_left_corner_offset.x,
303
+ config.front_right_corner_offset.y,
304
+ config.back_left_corner_offset.z,
305
+ ),
306
+ ),
307
+ bounding_nozzle_offsets=BoundingNozzlesOffsets(
308
+ back_left_offset=config.nozzle_map.back_left_nozzle_offset,
309
+ front_right_offset=config.nozzle_map.front_right_nozzle_offset,
310
+ ),
311
+ default_nozzle_map=config.nozzle_map,
312
+ lld_settings=config.pipette_lld_settings,
313
+ plunger_positions=config.plunger_positions,
314
+ shaft_ul_per_mm=config.shaft_ul_per_mm,
315
+ available_sensors=config.available_sensors,
316
+ )
317
+ self._state.flow_rates_by_id[
318
+ state_update.pipette_config.pipette_id
319
+ ] = config.flow_rates
320
+ self._state.nozzle_configuration_by_id[
321
+ state_update.pipette_config.pipette_id
322
+ ] = config.nozzle_map
323
+
324
+ def _update_pipette_nozzle_map(
325
+ self, state_update: update_types.StateUpdate
326
+ ) -> None:
327
+ if state_update.pipette_nozzle_map != update_types.NO_CHANGE:
328
+ self._state.nozzle_configuration_by_id[
329
+ state_update.pipette_nozzle_map.pipette_id
330
+ ] = state_update.pipette_nozzle_map.nozzle_map
331
+
332
+ def _update_ready_for_aspirate(
333
+ self, state_update: update_types.StateUpdate
334
+ ) -> None:
335
+ if state_update.ready_to_aspirate != update_types.NO_CHANGE:
336
+ self._state.ready_to_aspirate_by_id[
337
+ state_update.ready_to_aspirate.pipette_id
338
+ ] = state_update.ready_to_aspirate.ready_to_aspirate
339
+
340
+ def _update_volumes(self, state_update: update_types.StateUpdate) -> None:
341
+ if state_update.pipette_aspirated_fluid == update_types.NO_CHANGE:
342
+ return
343
+ # set the tip state to unclean, if an "empty" update has a clean_tip flag
344
+ # it will set it to true
345
+ self._state.has_clean_tips_by_id[
346
+ state_update.pipette_aspirated_fluid.pipette_id
347
+ ] = False
348
+
349
+ if state_update.pipette_aspirated_fluid.type == "aspirated":
350
+ self._update_aspirated(state_update.pipette_aspirated_fluid)
351
+ elif state_update.pipette_aspirated_fluid.type == "ejected":
352
+ self._update_ejected(state_update.pipette_aspirated_fluid)
353
+ elif state_update.pipette_aspirated_fluid.type == "empty":
354
+ self._update_empty(state_update.pipette_aspirated_fluid)
355
+ elif state_update.pipette_aspirated_fluid.type == "unknown":
356
+ self._update_unknown(state_update.pipette_aspirated_fluid)
357
+ else:
358
+ assert_never(state_update.pipette_aspirated_fluid.type)
359
+
360
+ def _update_aspirated(
361
+ self, update: update_types.PipetteAspiratedFluidUpdate
362
+ ) -> None:
363
+ if self._state.pipette_contents_by_id[update.pipette_id] is None:
364
+ self._state.pipette_contents_by_id[
365
+ update.pipette_id
366
+ ] = fluid_stack.FluidStack()
367
+
368
+ self._fluid_stack_log_if_empty(update.pipette_id).add_fluid(update.fluid)
369
+
370
+ def _update_ejected(self, update: update_types.PipetteEjectedFluidUpdate) -> None:
371
+ self._fluid_stack_log_if_empty(update.pipette_id).remove_fluid(update.volume)
372
+
373
+ def _update_empty(self, update: update_types.PipetteEmptyFluidUpdate) -> None:
374
+ self._state.pipette_contents_by_id[update.pipette_id] = fluid_stack.FluidStack()
375
+ self._state.has_clean_tips_by_id[update.pipette_id] = update.clean_tip
376
+
377
+ def _update_unknown(self, update: update_types.PipetteUnknownFluidUpdate) -> None:
378
+ self._state.pipette_contents_by_id[update.pipette_id] = None
379
+
380
+ def _fluid_stack_log_if_empty(self, pipette_id: str) -> fluid_stack.FluidStack:
381
+ stack = self._state.pipette_contents_by_id[pipette_id]
382
+ if stack is None:
383
+ LOG.error("Pipette state tried to alter an unknown-contents pipette")
384
+ return fluid_stack.FluidStack()
385
+ return stack
386
+
387
+
388
+ class PipetteView:
389
+ """Read-only view of computed pipettes state."""
390
+
391
+ _state: PipetteState
392
+
393
+ def __init__(self, state: PipetteState) -> None:
394
+ """Initialize the view with its backing state value."""
395
+ self._state = state
396
+
397
+ def get(self, pipette_id: str) -> LoadedPipette:
398
+ """Get pipette data by the pipette's unique identifier."""
399
+ try:
400
+ return self._state.pipettes_by_id[pipette_id]
401
+ except KeyError as e:
402
+ raise errors.PipetteNotLoadedError(
403
+ f"Pipette {pipette_id} not found."
404
+ ) from e
405
+
406
+ def get_mount(self, pipette_id: str) -> MountType:
407
+ """Get the pipette's mount."""
408
+ return self.get(pipette_id).mount
409
+
410
+ def get_all(self) -> List[LoadedPipette]:
411
+ """Get a list of all pipette entries in state."""
412
+ return list(self._state.pipettes_by_id.values())
413
+
414
+ def get_by_mount(self, mount: MountType) -> Optional[LoadedPipette]:
415
+ """Get pipette data by the pipette's mount."""
416
+ for pipette in self._state.pipettes_by_id.values():
417
+ if pipette.mount == mount:
418
+ return pipette
419
+ return None
420
+
421
+ def get_hardware_pipette(
422
+ self,
423
+ pipette_id: str,
424
+ attached_pipettes: Mapping[HwMount, Optional[PipetteDict]],
425
+ ) -> HardwarePipette:
426
+ """Get a pipette's hardware configuration and state by ID."""
427
+ pipette_data = self.get(pipette_id)
428
+ pipette_name = pipette_data.pipetteName
429
+ mount = pipette_data.mount
430
+
431
+ hw_mount = mount.to_hw_mount()
432
+ hw_config = attached_pipettes[hw_mount]
433
+
434
+ # TODO(mc, 2022-01-11): HW controller may return an empty dict for
435
+ # no pipette attached instead of `None`. Update when fixed in HWAPI
436
+ if not hw_config:
437
+ raise errors.PipetteNotAttachedError(f"No pipette attached on {mount}")
438
+
439
+ elif (
440
+ hw_config["name"] != pipette_name
441
+ and pipette_name not in hw_config["back_compat_names"]
442
+ ):
443
+ raise errors.PipetteNotAttachedError(
444
+ f"Found {hw_config['name']} on {mount}, "
445
+ f"but {pipette_id} is a {pipette_name}"
446
+ )
447
+
448
+ return HardwarePipette(mount=hw_mount, config=hw_config)
449
+
450
+ def get_current_location(self) -> Optional[CurrentPipetteLocation]:
451
+ """Get the last accessed location and which pipette accessed it."""
452
+ return self._state.current_location
453
+
454
+ def get_deck_point(self, pipette_id: str) -> Optional[DeckPoint]:
455
+ """Get the deck point of a pipette by ID, or None if it was not associated with the last move operation."""
456
+ loaded_pipette = self.get(pipette_id)
457
+ current_deck_point = self._state.current_deck_point
458
+ if loaded_pipette.mount == current_deck_point.mount:
459
+ return current_deck_point.deck_point
460
+ return None
461
+
462
+ def get_attached_tip(self, pipette_id: str) -> Optional[TipGeometry]:
463
+ """Get details of the pipette's attached tip.
464
+
465
+ Returns:
466
+ The tip's volume and length, or None if there is no tip attached,
467
+ """
468
+ try:
469
+ return self._state.attached_tip_by_id[pipette_id]
470
+ except KeyError as e:
471
+ raise errors.PipetteNotLoadedError(
472
+ f"Pipette {pipette_id} no found; unable to get attached tip."
473
+ ) from e
474
+
475
+ def get_all_attached_tips(self) -> List[Tuple[str, TipGeometry]]:
476
+ """Get a list of all attached tips.
477
+
478
+ Returns:
479
+ A list of pipette ID, tip details tuples.
480
+ """
481
+ return [
482
+ (pipette_id, tip)
483
+ for pipette_id, tip in self._state.attached_tip_by_id.items()
484
+ if tip is not None
485
+ ]
486
+
487
+ def get_tip_rack_well_picked_up_from(
488
+ self, pipette_id: str
489
+ ) -> Optional[LabwareWellId]:
490
+ """Get the tip rack well a tip has been has picked up from, if there currently is a tip attached."""
491
+ try:
492
+ return self._state.tip_source_by_id[pipette_id]
493
+ except KeyError as e:
494
+ raise errors.PipetteNotLoadedError(
495
+ f"Pipette {pipette_id} no found; unable to get last tip rack well accessed."
496
+ ) from e
497
+
498
+ def get_aspirated_volume(self, pipette_id: str) -> Optional[float]:
499
+ """Get the currently aspirated volume of a pipette by ID.
500
+
501
+ This is the volume currently displaced by the plunger relative to its bottom position,
502
+ regardless of whether that volume likely contains liquid or air. This makes it the right
503
+ function to call to know how much more volume the plunger may displace.
504
+
505
+ Returns:
506
+ The volume the pipette has aspirated.
507
+ None, after blow-out and the plunger is in an unsafe position.
508
+
509
+ Raises:
510
+ PipetteNotLoadedError: pipette ID does not exist.
511
+ TipNotAttachedError: if no tip is attached to the pipette.
512
+ """
513
+ self.validate_tip_state(pipette_id, True)
514
+
515
+ try:
516
+ stack = self._state.pipette_contents_by_id[pipette_id]
517
+ if stack is None:
518
+ return None
519
+ return stack.aspirated_volume()
520
+
521
+ except KeyError as e:
522
+ raise errors.PipetteNotLoadedError(
523
+ f"Pipette {pipette_id} not found; unable to get current volume."
524
+ ) from e
525
+
526
+ def get_has_clean_tip(self, pipette_id: str) -> bool:
527
+ """Get if the tip of a pipette by ID is clean.
528
+
529
+ This is only true directly after a pick up tip, once any kind of aspirate happens
530
+ it is no longer clean
531
+
532
+ Returns:
533
+ True if the tip is clean
534
+ False if it is unclean
535
+
536
+ Raises:
537
+ PipetteNotLoadedError: pipette ID does not exist.
538
+ TipNotAttachedError: if no tip is attached to the pipette.
539
+ """
540
+ self.validate_tip_state(pipette_id, True)
541
+
542
+ try:
543
+ return self._state.has_clean_tips_by_id[pipette_id]
544
+ except KeyError as e:
545
+ raise errors.PipetteNotLoadedError(
546
+ f"Pipette {pipette_id} not found; unable to get current volume."
547
+ ) from e
548
+
549
+ def get_liquid_dispensed_by_ejecting_volume(
550
+ self, pipette_id: str, volume: float
551
+ ) -> Optional[float]:
552
+ """Get the amount of liquid (not air) that will be dispensed if the pipette ejects a specified volume.
553
+
554
+ For instance, if the pipette contains, in vertical order,
555
+ 10 ul air
556
+ 80 ul liquid
557
+ 5 ul air
558
+
559
+ then dispensing 10ul would result in 5ul of liquid; dispensing 85 ul would result in 80ul liquid; dispensing
560
+ 95ul would result in 80ul liquid.
561
+
562
+ Returns:
563
+ The volume of liquid that would be dispensed by the requested volume.
564
+ None, after blow-out or when the plunger is in an unsafe position.
565
+
566
+ Raises:
567
+ PipetteNotLoadedError: pipette ID does not exist.
568
+ TipnotAttachedError: No tip is attached to the pipette.
569
+ """
570
+ self.validate_tip_state(pipette_id, True)
571
+
572
+ try:
573
+ stack = self._state.pipette_contents_by_id[pipette_id]
574
+ if stack is None:
575
+ return None
576
+ return stack.liquid_part_of_dispense_volume(volume)
577
+
578
+ except KeyError as e:
579
+ raise errors.PipetteNotLoadedError(
580
+ f"Pipette {pipette_id} not found; unable to get current liquid volume."
581
+ ) from e
582
+
583
+ def get_working_volume(self, pipette_id: str) -> float:
584
+ """Get the working maximum volume of a pipette by ID.
585
+
586
+ Raises:
587
+ PipetteNotLoadedError: pipette ID does not exist.
588
+ TipNotAttachedError: if no tip is attached to the pipette.
589
+ """
590
+ max_volume = self.get_maximum_volume(pipette_id)
591
+ attached_tip = self.get_attached_tip(pipette_id)
592
+
593
+ if not attached_tip:
594
+ raise errors.TipNotAttachedError(
595
+ f"Pipette {pipette_id} has no tip attached; unable to calculate working maximum volume."
596
+ )
597
+
598
+ return min(attached_tip.volume, max_volume)
599
+
600
+ def get_available_volume(self, pipette_id: str) -> Optional[float]:
601
+ """Get the available volume of a pipette by ID."""
602
+ working_volume = self.get_working_volume(pipette_id)
603
+ current_volume = self.get_aspirated_volume(pipette_id)
604
+
605
+ return max(0.0, working_volume - current_volume) if current_volume else None
606
+
607
+ def get_pipette_lld_settings(
608
+ self, pipette_id: str
609
+ ) -> Optional[Dict[str, Dict[str, float]]]:
610
+ """Get the liquid level settings for all possible tips for a single pipette."""
611
+ return self.get_config(pipette_id).lld_settings
612
+
613
+ def get_current_tip_lld_settings(self, pipette_id: str) -> float:
614
+ """Get the liquid level settings for pipette and its current tip."""
615
+ attached_tip = self.get_attached_tip(pipette_id)
616
+ if attached_tip is None or attached_tip.volume is None:
617
+ return 0
618
+ lld_settings = self.get_pipette_lld_settings(pipette_id)
619
+ tipVolume = "t" + str(int(attached_tip.volume))
620
+ if (
621
+ lld_settings is None
622
+ or lld_settings[tipVolume] is None
623
+ or lld_settings[tipVolume]["minHeight"] is None
624
+ ):
625
+ return 0
626
+ return float(lld_settings[tipVolume]["minHeight"])
627
+
628
+ def validate_tip_state(self, pipette_id: str, expected_has_tip: bool) -> None:
629
+ """Validate that a pipette's tip state matches expectations."""
630
+ attached_tip = self.get_attached_tip(pipette_id)
631
+
632
+ if expected_has_tip is True and attached_tip is None:
633
+ raise errors.TipNotAttachedError(
634
+ "Pipette should have a tip attached, but does not."
635
+ )
636
+ if expected_has_tip is False and attached_tip is not None:
637
+ raise errors.TipAttachedError(
638
+ "Pipette should not have a tip attached, but does."
639
+ )
640
+
641
+ def get_movement_speed(
642
+ self, pipette_id: str, requested_speed: Optional[float] = None
643
+ ) -> Optional[float]:
644
+ """Return the given pipette's requested or current movement speed."""
645
+ return requested_speed or self._state.movement_speed_by_id[pipette_id]
646
+
647
+ def get_config(self, pipette_id: str) -> StaticPipetteConfig:
648
+ """Get the static pipette configuration by pipette id."""
649
+ try:
650
+ return self._state.static_config_by_id[pipette_id]
651
+ except KeyError as e:
652
+ raise errors.PipetteNotLoadedError(
653
+ f"Pipette {pipette_id} not found; unable to get pipette configuration."
654
+ ) from e
655
+
656
+ def get_model_name(self, pipette_id: str) -> str:
657
+ """Return the given pipette's model name."""
658
+ return self.get_config(pipette_id).model
659
+
660
+ def get_display_name(self, pipette_id: str) -> str:
661
+ """Return the given pipette's display name."""
662
+ return self.get_config(pipette_id).display_name
663
+
664
+ def get_serial_number(self, pipette_id: str) -> str:
665
+ """Get the serial number of the pipette."""
666
+ return self.get_config(pipette_id).serial_number
667
+
668
+ def get_channels(self, pipette_id: str) -> int:
669
+ """Return the max channels of the pipette."""
670
+ return self.get_config(pipette_id).channels
671
+
672
+ def get_active_channels(self, pipette_id: str) -> int:
673
+ """Get the number of channels being used in the given pipette's configuration."""
674
+ return self.get_nozzle_configuration(pipette_id).tip_count
675
+
676
+ def get_minimum_volume(self, pipette_id: str) -> float:
677
+ """Return the given pipette's minimum volume."""
678
+ return self.get_config(pipette_id).min_volume
679
+
680
+ def get_maximum_volume(self, pipette_id: str) -> float:
681
+ """Return the given pipette's maximum volume."""
682
+ return self.get_config(pipette_id).max_volume
683
+
684
+ def get_instrument_max_height_ot2(self, pipette_id: str) -> float:
685
+ """Get calculated max instrument height for an OT-2."""
686
+ config = self.get_config(pipette_id)
687
+ return config.home_position - Z_RETRACT_DISTANCE + config.nozzle_offset_z
688
+
689
+ def get_return_tip_scale(self, pipette_id: str) -> float:
690
+ """Return the given pipette's return tip height scale."""
691
+ max_volume = self.get_maximum_volume(pipette_id)
692
+ working_volume = max_volume
693
+ tip = self.get_attached_tip(pipette_id)
694
+ if tip:
695
+ working_volume = tip.volume
696
+
697
+ if working_volume in self.get_config(pipette_id).tip_configuration_lookup_table:
698
+ tip_lookup = self.get_config(pipette_id).tip_configuration_lookup_table[
699
+ working_volume
700
+ ]
701
+ else:
702
+ tip_lookup = self.get_config(pipette_id).tip_configuration_lookup_table[
703
+ max_volume
704
+ ]
705
+ return tip_lookup.default_return_tip_height
706
+
707
+ def get_flow_rates(self, pipette_id: str) -> FlowRates:
708
+ """Get the default flow rates for the pipette."""
709
+ try:
710
+ return self._state.flow_rates_by_id[pipette_id]
711
+ except KeyError as e:
712
+ raise errors.PipetteNotLoadedError(
713
+ f"Pipette {pipette_id} not found; unable to get pipette flow rates."
714
+ ) from e
715
+
716
+ def get_nominal_tip_overlap(self, pipette_id: str, labware_uri: str) -> float:
717
+ """Get the nominal tip overlap for a given labware from config."""
718
+ tip_overlaps_by_uri = self.get_config(pipette_id).nominal_tip_overlap
719
+
720
+ try:
721
+ return tip_overlaps_by_uri[labware_uri]
722
+ except KeyError:
723
+ return tip_overlaps_by_uri.get("default", 0)
724
+
725
+ def get_z_axis(self, pipette_id: str) -> MotorAxis:
726
+ """Get the MotorAxis representing this pipette's Z stage."""
727
+ mount = self.get(pipette_id).mount
728
+ return MotorAxis.LEFT_Z if mount == MountType.LEFT else MotorAxis.RIGHT_Z
729
+
730
+ def get_plunger_axis(self, pipette_id: str) -> MotorAxis:
731
+ """Get the MotorAxis representing this pipette's plunger."""
732
+ mount = self.get(pipette_id).mount
733
+ return (
734
+ MotorAxis.LEFT_PLUNGER
735
+ if mount == MountType.LEFT
736
+ else MotorAxis.RIGHT_PLUNGER
737
+ )
738
+
739
+ def get_nozzle_layout_type(self, pipette_id: str) -> NozzleConfigurationType:
740
+ """Get the current set nozzle layout configuration."""
741
+ nozzle_map_for_pipette = self._state.nozzle_configuration_by_id[pipette_id]
742
+ return nozzle_map_for_pipette.configuration
743
+
744
+ def get_is_partially_configured(self, pipette_id: str) -> bool:
745
+ """Determine if the provided pipette is partially configured."""
746
+ return self.get_nozzle_layout_type(pipette_id) != NozzleConfigurationType.FULL
747
+
748
+ def get_primary_nozzle(self, pipette_id: str) -> str:
749
+ """Get the primary nozzle, if any, related to the given pipette's nozzle configuration."""
750
+ nozzle_map = self._state.nozzle_configuration_by_id[pipette_id]
751
+ return nozzle_map.starting_nozzle
752
+
753
+ def get_nozzle_configurations(self) -> Dict[str, NozzleMap]:
754
+ """Get the nozzle maps of all pipettes, keyed by pipette ID."""
755
+ return self._state.nozzle_configuration_by_id.copy()
756
+
757
+ def get_nozzle_configuration(self, pipette_id: str) -> NozzleMap:
758
+ """Get the nozzle map of the pipette."""
759
+ return self._state.nozzle_configuration_by_id[pipette_id]
760
+
761
+ def _get_critical_point_offset_without_tip(
762
+ self, pipette_id: str, critical_point: Optional[CriticalPoint]
763
+ ) -> Point:
764
+ """Get the offset of the specified critical point from pipette's mount position."""
765
+ nozzle_map = self._state.nozzle_configuration_by_id[pipette_id]
766
+ match critical_point:
767
+ case CriticalPoint.INSTRUMENT_XY_CENTER:
768
+ return nozzle_map.instrument_xy_center_offset
769
+ case CriticalPoint.XY_CENTER:
770
+ return nozzle_map.xy_center_offset
771
+ case CriticalPoint.Y_CENTER:
772
+ return nozzle_map.y_center_offset
773
+ case CriticalPoint.FRONT_NOZZLE:
774
+ return nozzle_map.front_nozzle_offset
775
+ case _:
776
+ return nozzle_map.starting_nozzle_offset
777
+
778
+ def get_pipette_bounding_nozzle_offsets(
779
+ self, pipette_id: str
780
+ ) -> BoundingNozzlesOffsets:
781
+ """Get the nozzle offsets of the pipette's bounding nozzles."""
782
+ return self.get_config(pipette_id).bounding_nozzle_offsets
783
+
784
+ def get_pipette_bounding_box(self, pipette_id: str) -> PipetteBoundingBoxOffsets:
785
+ """Get the bounding box of the pipette."""
786
+ return self.get_config(pipette_id).pipette_bounding_box_offsets
787
+
788
+ # TODO (spp, 2024-09-17): in order to find the position of pipette at destination,
789
+ # this method repeats the same steps that waypoints builder does while finding
790
+ # waypoints to move to. We should consolidate these steps into a shared entity
791
+ # so that the deck conflict checker and movement plan builder always remain in sync.
792
+ def get_pipette_bounds_at_specified_move_to_position(
793
+ self,
794
+ pipette_id: str,
795
+ destination_position: Point,
796
+ critical_point: Optional[CriticalPoint],
797
+ ) -> Tuple[Point, Point, Point, Point]:
798
+ """Get the pipette's bounding box position when critical point is at the destination position.
799
+
800
+ Returns a tuple of the pipette's bounding box position in deck coordinates as-
801
+ (back_left_bound, front_right_bound, back_right_bound, front_left_bound)
802
+ Bounding box of the pipette includes the pipette's outer casing as well as nozzles.
803
+ """
804
+ tip = self.get_attached_tip(pipette_id)
805
+
806
+ # *Offset* of pipette's critical point w.r.t pipette mount
807
+ critical_point_offset = self._get_critical_point_offset_without_tip(
808
+ pipette_id, critical_point
809
+ )
810
+
811
+ # Position of the above critical point at destination, in deck coordinates
812
+ critical_point_position = destination_position + Point(
813
+ x=0, y=0, z=tip.length if tip else 0
814
+ )
815
+
816
+ # Get the pipette bounding box coordinates
817
+ pipette_bounds_offsets = self.get_config(
818
+ pipette_id
819
+ ).pipette_bounding_box_offsets
820
+ pip_back_left_bound = (
821
+ critical_point_position
822
+ - critical_point_offset
823
+ + pipette_bounds_offsets.back_left_corner
824
+ )
825
+ pip_front_right_bound = (
826
+ critical_point_position
827
+ - critical_point_offset
828
+ + pipette_bounds_offsets.front_right_corner
829
+ )
830
+ pip_back_right_bound = Point(
831
+ pip_front_right_bound.x, pip_back_left_bound.y, pip_front_right_bound.z
832
+ )
833
+ pip_front_left_bound = Point(
834
+ pip_back_left_bound.x, pip_front_right_bound.y, pip_back_left_bound.z
835
+ )
836
+ return (
837
+ pip_back_left_bound,
838
+ pip_front_right_bound,
839
+ pip_back_right_bound,
840
+ pip_front_left_bound,
841
+ )
842
+
843
+ def get_pipette_supports_pressure(self, pipette_id: str) -> bool:
844
+ """Return if this pipette supports a pressure sensor."""
845
+ return (
846
+ "pressure"
847
+ in self._state.static_config_by_id[pipette_id].available_sensors.sensors
848
+ )
849
+
850
+ def get_liquid_presence_detection(self, pipette_id: str) -> bool:
851
+ """Determine if liquid presence detection is enabled for this pipette."""
852
+ try:
853
+ return self._state.liquid_presence_detection_by_id[pipette_id]
854
+ except KeyError as e:
855
+ raise errors.PipetteNotLoadedError(
856
+ f"Pipette {pipette_id} not found; unable to determine if pipette liquid presence detection enabled."
857
+ ) from e
858
+
859
+ def get_nozzle_configuration_supports_lld(self, pipette_id: str) -> bool:
860
+ """Determine if the current partial tip configuration supports LLD."""
861
+ nozzle_map = self.get_nozzle_configuration(pipette_id)
862
+ if (
863
+ nozzle_map.physical_nozzle_count == 96
864
+ and nozzle_map.back_left != nozzle_map.full_instrument_back_left
865
+ and nozzle_map.front_right != nozzle_map.full_instrument_front_right
866
+ ):
867
+ return False
868
+ return True
869
+
870
+ def lookup_volume_to_mm_conversion(
871
+ self, pipette_id: str, volume: float, action: str
872
+ ) -> float:
873
+ """Get the volumn to mm conversion for a pipette."""
874
+ try:
875
+ lookup_volume = self.get_working_volume(pipette_id)
876
+ except errors.TipNotAttachedError:
877
+ lookup_volume = self.get_maximum_volume(pipette_id)
878
+
879
+ pipette_config = self.get_config(pipette_id)
880
+ lookup_table_from_config = pipette_config.tip_configuration_lookup_table
881
+ try:
882
+ tip_settings = lookup_table_from_config[lookup_volume]
883
+ except KeyError:
884
+ tip_settings = list(lookup_table_from_config.values())[0]
885
+ return calculate_ul_per_mm(
886
+ volume,
887
+ cast(UlPerMmAction, action),
888
+ tip_settings,
889
+ shaft_ul_per_mm=pipette_config.shaft_ul_per_mm,
890
+ )
891
+
892
+ def lookup_plunger_position_name(
893
+ self, pipette_id: str, position_name: str
894
+ ) -> float:
895
+ """Get the plunger position provided for the given pipette id."""
896
+ return self.get_config(pipette_id).plunger_positions[position_name]
897
+
898
+ def get_ready_to_aspirate(self, pipette_id: str) -> bool:
899
+ """Get if the provided pipette is ready to aspirate for the given pipette id."""
900
+ try:
901
+ return self._state.ready_to_aspirate_by_id[pipette_id]
902
+ except KeyError as e:
903
+ raise errors.PipetteNotLoadedError(
904
+ f"Pipette {pipette_id} not found; unable to determine if pipette ready to aspirate."
905
+ ) from e