opentrons 8.6.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of opentrons might be problematic. Click here for more details.

Files changed (600) hide show
  1. opentrons/__init__.py +150 -0
  2. opentrons/_version.py +34 -0
  3. opentrons/calibration_storage/__init__.py +54 -0
  4. opentrons/calibration_storage/deck_configuration.py +62 -0
  5. opentrons/calibration_storage/encoder_decoder.py +31 -0
  6. opentrons/calibration_storage/file_operators.py +142 -0
  7. opentrons/calibration_storage/helpers.py +103 -0
  8. opentrons/calibration_storage/ot2/__init__.py +34 -0
  9. opentrons/calibration_storage/ot2/deck_attitude.py +85 -0
  10. opentrons/calibration_storage/ot2/mark_bad_calibration.py +27 -0
  11. opentrons/calibration_storage/ot2/models/__init__.py +0 -0
  12. opentrons/calibration_storage/ot2/models/v1.py +149 -0
  13. opentrons/calibration_storage/ot2/pipette_offset.py +129 -0
  14. opentrons/calibration_storage/ot2/tip_length.py +281 -0
  15. opentrons/calibration_storage/ot3/__init__.py +31 -0
  16. opentrons/calibration_storage/ot3/deck_attitude.py +83 -0
  17. opentrons/calibration_storage/ot3/gripper_offset.py +156 -0
  18. opentrons/calibration_storage/ot3/models/__init__.py +0 -0
  19. opentrons/calibration_storage/ot3/models/v1.py +122 -0
  20. opentrons/calibration_storage/ot3/module_offset.py +138 -0
  21. opentrons/calibration_storage/ot3/pipette_offset.py +95 -0
  22. opentrons/calibration_storage/types.py +45 -0
  23. opentrons/cli/__init__.py +21 -0
  24. opentrons/cli/__main__.py +5 -0
  25. opentrons/cli/analyze.py +501 -0
  26. opentrons/config/__init__.py +631 -0
  27. opentrons/config/advanced_settings.py +871 -0
  28. opentrons/config/defaults_ot2.py +214 -0
  29. opentrons/config/defaults_ot3.py +499 -0
  30. opentrons/config/feature_flags.py +86 -0
  31. opentrons/config/gripper_config.py +55 -0
  32. opentrons/config/reset.py +203 -0
  33. opentrons/config/robot_configs.py +187 -0
  34. opentrons/config/types.py +183 -0
  35. opentrons/drivers/__init__.py +0 -0
  36. opentrons/drivers/absorbance_reader/__init__.py +11 -0
  37. opentrons/drivers/absorbance_reader/abstract.py +72 -0
  38. opentrons/drivers/absorbance_reader/async_byonoy.py +352 -0
  39. opentrons/drivers/absorbance_reader/driver.py +81 -0
  40. opentrons/drivers/absorbance_reader/hid_protocol.py +161 -0
  41. opentrons/drivers/absorbance_reader/simulator.py +84 -0
  42. opentrons/drivers/asyncio/__init__.py +0 -0
  43. opentrons/drivers/asyncio/communication/__init__.py +22 -0
  44. opentrons/drivers/asyncio/communication/async_serial.py +183 -0
  45. opentrons/drivers/asyncio/communication/errors.py +88 -0
  46. opentrons/drivers/asyncio/communication/serial_connection.py +552 -0
  47. opentrons/drivers/command_builder.py +102 -0
  48. opentrons/drivers/flex_stacker/__init__.py +13 -0
  49. opentrons/drivers/flex_stacker/abstract.py +214 -0
  50. opentrons/drivers/flex_stacker/driver.py +768 -0
  51. opentrons/drivers/flex_stacker/errors.py +68 -0
  52. opentrons/drivers/flex_stacker/simulator.py +309 -0
  53. opentrons/drivers/flex_stacker/types.py +367 -0
  54. opentrons/drivers/flex_stacker/utils.py +19 -0
  55. opentrons/drivers/heater_shaker/__init__.py +5 -0
  56. opentrons/drivers/heater_shaker/abstract.py +76 -0
  57. opentrons/drivers/heater_shaker/driver.py +204 -0
  58. opentrons/drivers/heater_shaker/simulator.py +94 -0
  59. opentrons/drivers/mag_deck/__init__.py +6 -0
  60. opentrons/drivers/mag_deck/abstract.py +44 -0
  61. opentrons/drivers/mag_deck/driver.py +208 -0
  62. opentrons/drivers/mag_deck/simulator.py +63 -0
  63. opentrons/drivers/rpi_drivers/__init__.py +33 -0
  64. opentrons/drivers/rpi_drivers/dev_types.py +94 -0
  65. opentrons/drivers/rpi_drivers/gpio.py +282 -0
  66. opentrons/drivers/rpi_drivers/gpio_simulator.py +127 -0
  67. opentrons/drivers/rpi_drivers/interfaces.py +15 -0
  68. opentrons/drivers/rpi_drivers/types.py +364 -0
  69. opentrons/drivers/rpi_drivers/usb.py +102 -0
  70. opentrons/drivers/rpi_drivers/usb_simulator.py +22 -0
  71. opentrons/drivers/serial_communication.py +151 -0
  72. opentrons/drivers/smoothie_drivers/__init__.py +4 -0
  73. opentrons/drivers/smoothie_drivers/connection.py +51 -0
  74. opentrons/drivers/smoothie_drivers/constants.py +121 -0
  75. opentrons/drivers/smoothie_drivers/driver_3_0.py +1933 -0
  76. opentrons/drivers/smoothie_drivers/errors.py +49 -0
  77. opentrons/drivers/smoothie_drivers/parse_utils.py +143 -0
  78. opentrons/drivers/smoothie_drivers/simulator.py +99 -0
  79. opentrons/drivers/smoothie_drivers/types.py +16 -0
  80. opentrons/drivers/temp_deck/__init__.py +10 -0
  81. opentrons/drivers/temp_deck/abstract.py +54 -0
  82. opentrons/drivers/temp_deck/driver.py +197 -0
  83. opentrons/drivers/temp_deck/simulator.py +57 -0
  84. opentrons/drivers/thermocycler/__init__.py +12 -0
  85. opentrons/drivers/thermocycler/abstract.py +99 -0
  86. opentrons/drivers/thermocycler/driver.py +395 -0
  87. opentrons/drivers/thermocycler/simulator.py +126 -0
  88. opentrons/drivers/types.py +107 -0
  89. opentrons/drivers/utils.py +222 -0
  90. opentrons/execute.py +742 -0
  91. opentrons/hardware_control/__init__.py +65 -0
  92. opentrons/hardware_control/__main__.py +77 -0
  93. opentrons/hardware_control/adapters.py +98 -0
  94. opentrons/hardware_control/api.py +1347 -0
  95. opentrons/hardware_control/backends/__init__.py +7 -0
  96. opentrons/hardware_control/backends/controller.py +400 -0
  97. opentrons/hardware_control/backends/errors.py +9 -0
  98. opentrons/hardware_control/backends/estop_state.py +164 -0
  99. opentrons/hardware_control/backends/flex_protocol.py +497 -0
  100. opentrons/hardware_control/backends/ot3controller.py +1930 -0
  101. opentrons/hardware_control/backends/ot3simulator.py +900 -0
  102. opentrons/hardware_control/backends/ot3utils.py +664 -0
  103. opentrons/hardware_control/backends/simulator.py +442 -0
  104. opentrons/hardware_control/backends/status_bar_state.py +240 -0
  105. opentrons/hardware_control/backends/subsystem_manager.py +431 -0
  106. opentrons/hardware_control/backends/tip_presence_manager.py +173 -0
  107. opentrons/hardware_control/backends/types.py +14 -0
  108. opentrons/hardware_control/constants.py +6 -0
  109. opentrons/hardware_control/dev_types.py +125 -0
  110. opentrons/hardware_control/emulation/__init__.py +0 -0
  111. opentrons/hardware_control/emulation/abstract_emulator.py +21 -0
  112. opentrons/hardware_control/emulation/app.py +56 -0
  113. opentrons/hardware_control/emulation/connection_handler.py +38 -0
  114. opentrons/hardware_control/emulation/heater_shaker.py +150 -0
  115. opentrons/hardware_control/emulation/magdeck.py +60 -0
  116. opentrons/hardware_control/emulation/module_server/__init__.py +8 -0
  117. opentrons/hardware_control/emulation/module_server/client.py +78 -0
  118. opentrons/hardware_control/emulation/module_server/helpers.py +130 -0
  119. opentrons/hardware_control/emulation/module_server/models.py +31 -0
  120. opentrons/hardware_control/emulation/module_server/server.py +110 -0
  121. opentrons/hardware_control/emulation/parser.py +74 -0
  122. opentrons/hardware_control/emulation/proxy.py +241 -0
  123. opentrons/hardware_control/emulation/run_emulator.py +68 -0
  124. opentrons/hardware_control/emulation/scripts/__init__.py +0 -0
  125. opentrons/hardware_control/emulation/scripts/run_app.py +54 -0
  126. opentrons/hardware_control/emulation/scripts/run_module_emulator.py +72 -0
  127. opentrons/hardware_control/emulation/scripts/run_smoothie.py +37 -0
  128. opentrons/hardware_control/emulation/settings.py +119 -0
  129. opentrons/hardware_control/emulation/simulations.py +133 -0
  130. opentrons/hardware_control/emulation/smoothie.py +192 -0
  131. opentrons/hardware_control/emulation/tempdeck.py +69 -0
  132. opentrons/hardware_control/emulation/thermocycler.py +128 -0
  133. opentrons/hardware_control/emulation/types.py +10 -0
  134. opentrons/hardware_control/emulation/util.py +38 -0
  135. opentrons/hardware_control/errors.py +43 -0
  136. opentrons/hardware_control/execution_manager.py +164 -0
  137. opentrons/hardware_control/instruments/__init__.py +5 -0
  138. opentrons/hardware_control/instruments/instrument_abc.py +39 -0
  139. opentrons/hardware_control/instruments/ot2/__init__.py +0 -0
  140. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +152 -0
  141. opentrons/hardware_control/instruments/ot2/pipette.py +777 -0
  142. opentrons/hardware_control/instruments/ot2/pipette_handler.py +995 -0
  143. opentrons/hardware_control/instruments/ot3/__init__.py +0 -0
  144. opentrons/hardware_control/instruments/ot3/gripper.py +420 -0
  145. opentrons/hardware_control/instruments/ot3/gripper_handler.py +173 -0
  146. opentrons/hardware_control/instruments/ot3/instrument_calibration.py +214 -0
  147. opentrons/hardware_control/instruments/ot3/pipette.py +858 -0
  148. opentrons/hardware_control/instruments/ot3/pipette_handler.py +1030 -0
  149. opentrons/hardware_control/module_control.py +332 -0
  150. opentrons/hardware_control/modules/__init__.py +69 -0
  151. opentrons/hardware_control/modules/absorbance_reader.py +373 -0
  152. opentrons/hardware_control/modules/errors.py +7 -0
  153. opentrons/hardware_control/modules/flex_stacker.py +948 -0
  154. opentrons/hardware_control/modules/heater_shaker.py +426 -0
  155. opentrons/hardware_control/modules/lid_temp_status.py +35 -0
  156. opentrons/hardware_control/modules/magdeck.py +233 -0
  157. opentrons/hardware_control/modules/mod_abc.py +245 -0
  158. opentrons/hardware_control/modules/module_calibration.py +93 -0
  159. opentrons/hardware_control/modules/plate_temp_status.py +61 -0
  160. opentrons/hardware_control/modules/tempdeck.py +299 -0
  161. opentrons/hardware_control/modules/thermocycler.py +731 -0
  162. opentrons/hardware_control/modules/types.py +417 -0
  163. opentrons/hardware_control/modules/update.py +255 -0
  164. opentrons/hardware_control/modules/utils.py +73 -0
  165. opentrons/hardware_control/motion_utilities.py +318 -0
  166. opentrons/hardware_control/nozzle_manager.py +422 -0
  167. opentrons/hardware_control/ot3_calibration.py +1171 -0
  168. opentrons/hardware_control/ot3api.py +3227 -0
  169. opentrons/hardware_control/pause_manager.py +31 -0
  170. opentrons/hardware_control/poller.py +112 -0
  171. opentrons/hardware_control/protocols/__init__.py +106 -0
  172. opentrons/hardware_control/protocols/asyncio_configurable.py +11 -0
  173. opentrons/hardware_control/protocols/calibratable.py +45 -0
  174. opentrons/hardware_control/protocols/chassis_accessory_manager.py +90 -0
  175. opentrons/hardware_control/protocols/configurable.py +48 -0
  176. opentrons/hardware_control/protocols/event_sourcer.py +18 -0
  177. opentrons/hardware_control/protocols/execution_controllable.py +33 -0
  178. opentrons/hardware_control/protocols/flex_calibratable.py +96 -0
  179. opentrons/hardware_control/protocols/flex_instrument_configurer.py +52 -0
  180. opentrons/hardware_control/protocols/gripper_controller.py +55 -0
  181. opentrons/hardware_control/protocols/hardware_manager.py +51 -0
  182. opentrons/hardware_control/protocols/identifiable.py +16 -0
  183. opentrons/hardware_control/protocols/instrument_configurer.py +206 -0
  184. opentrons/hardware_control/protocols/liquid_handler.py +266 -0
  185. opentrons/hardware_control/protocols/module_provider.py +16 -0
  186. opentrons/hardware_control/protocols/motion_controller.py +243 -0
  187. opentrons/hardware_control/protocols/position_estimator.py +45 -0
  188. opentrons/hardware_control/protocols/simulatable.py +10 -0
  189. opentrons/hardware_control/protocols/stoppable.py +9 -0
  190. opentrons/hardware_control/protocols/types.py +27 -0
  191. opentrons/hardware_control/robot_calibration.py +224 -0
  192. opentrons/hardware_control/scripts/README.md +28 -0
  193. opentrons/hardware_control/scripts/__init__.py +1 -0
  194. opentrons/hardware_control/scripts/gripper_control.py +208 -0
  195. opentrons/hardware_control/scripts/ot3gripper +7 -0
  196. opentrons/hardware_control/scripts/ot3repl +7 -0
  197. opentrons/hardware_control/scripts/repl.py +187 -0
  198. opentrons/hardware_control/scripts/tc_control.py +97 -0
  199. opentrons/hardware_control/simulator_setup.py +260 -0
  200. opentrons/hardware_control/thread_manager.py +431 -0
  201. opentrons/hardware_control/threaded_async_lock.py +97 -0
  202. opentrons/hardware_control/types.py +792 -0
  203. opentrons/hardware_control/util.py +234 -0
  204. opentrons/legacy_broker.py +53 -0
  205. opentrons/legacy_commands/__init__.py +1 -0
  206. opentrons/legacy_commands/commands.py +483 -0
  207. opentrons/legacy_commands/helpers.py +153 -0
  208. opentrons/legacy_commands/module_commands.py +215 -0
  209. opentrons/legacy_commands/protocol_commands.py +54 -0
  210. opentrons/legacy_commands/publisher.py +155 -0
  211. opentrons/legacy_commands/robot_commands.py +51 -0
  212. opentrons/legacy_commands/types.py +1115 -0
  213. opentrons/motion_planning/__init__.py +32 -0
  214. opentrons/motion_planning/adjacent_slots_getters.py +168 -0
  215. opentrons/motion_planning/deck_conflict.py +396 -0
  216. opentrons/motion_planning/errors.py +35 -0
  217. opentrons/motion_planning/types.py +42 -0
  218. opentrons/motion_planning/waypoints.py +218 -0
  219. opentrons/ordered_set.py +138 -0
  220. opentrons/protocol_api/__init__.py +105 -0
  221. opentrons/protocol_api/_liquid.py +157 -0
  222. opentrons/protocol_api/_liquid_properties.py +814 -0
  223. opentrons/protocol_api/_nozzle_layout.py +31 -0
  224. opentrons/protocol_api/_parameter_context.py +300 -0
  225. opentrons/protocol_api/_parameters.py +31 -0
  226. opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
  227. opentrons/protocol_api/_types.py +43 -0
  228. opentrons/protocol_api/config.py +23 -0
  229. opentrons/protocol_api/core/__init__.py +23 -0
  230. opentrons/protocol_api/core/common.py +33 -0
  231. opentrons/protocol_api/core/core_map.py +74 -0
  232. opentrons/protocol_api/core/engine/__init__.py +22 -0
  233. opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
  234. opentrons/protocol_api/core/engine/deck_conflict.py +348 -0
  235. opentrons/protocol_api/core/engine/exceptions.py +19 -0
  236. opentrons/protocol_api/core/engine/instrument.py +2391 -0
  237. opentrons/protocol_api/core/engine/labware.py +238 -0
  238. opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
  239. opentrons/protocol_api/core/engine/module_core.py +1025 -0
  240. opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
  241. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
  242. opentrons/protocol_api/core/engine/point_calculations.py +64 -0
  243. opentrons/protocol_api/core/engine/protocol.py +1153 -0
  244. opentrons/protocol_api/core/engine/robot.py +139 -0
  245. opentrons/protocol_api/core/engine/stringify.py +74 -0
  246. opentrons/protocol_api/core/engine/transfer_components_executor.py +990 -0
  247. opentrons/protocol_api/core/engine/well.py +241 -0
  248. opentrons/protocol_api/core/instrument.py +459 -0
  249. opentrons/protocol_api/core/labware.py +151 -0
  250. opentrons/protocol_api/core/legacy/__init__.py +11 -0
  251. opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
  252. opentrons/protocol_api/core/legacy/deck.py +369 -0
  253. opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
  254. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
  255. opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
  256. opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
  257. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
  258. opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
  259. opentrons/protocol_api/core/legacy/load_info.py +67 -0
  260. opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
  261. opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
  262. opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
  263. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
  264. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
  265. opentrons/protocol_api/core/module.py +484 -0
  266. opentrons/protocol_api/core/protocol.py +311 -0
  267. opentrons/protocol_api/core/robot.py +51 -0
  268. opentrons/protocol_api/core/well.py +116 -0
  269. opentrons/protocol_api/core/well_grid.py +45 -0
  270. opentrons/protocol_api/create_protocol_context.py +177 -0
  271. opentrons/protocol_api/deck.py +223 -0
  272. opentrons/protocol_api/disposal_locations.py +244 -0
  273. opentrons/protocol_api/instrument_context.py +3212 -0
  274. opentrons/protocol_api/labware.py +1579 -0
  275. opentrons/protocol_api/module_contexts.py +1425 -0
  276. opentrons/protocol_api/module_validation_and_errors.py +61 -0
  277. opentrons/protocol_api/protocol_context.py +1688 -0
  278. opentrons/protocol_api/robot_context.py +303 -0
  279. opentrons/protocol_api/validation.py +761 -0
  280. opentrons/protocol_engine/__init__.py +155 -0
  281. opentrons/protocol_engine/actions/__init__.py +65 -0
  282. opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
  283. opentrons/protocol_engine/actions/action_handler.py +13 -0
  284. opentrons/protocol_engine/actions/actions.py +302 -0
  285. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  286. opentrons/protocol_engine/clients/__init__.py +5 -0
  287. opentrons/protocol_engine/clients/sync_client.py +174 -0
  288. opentrons/protocol_engine/clients/transports.py +197 -0
  289. opentrons/protocol_engine/commands/__init__.py +757 -0
  290. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
  291. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
  292. opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
  293. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
  294. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
  295. opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
  296. opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
  297. opentrons/protocol_engine/commands/aspirate.py +244 -0
  298. opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
  299. opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
  300. opentrons/protocol_engine/commands/blow_out.py +146 -0
  301. opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
  302. opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
  303. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
  304. opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
  305. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
  306. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
  307. opentrons/protocol_engine/commands/command.py +308 -0
  308. opentrons/protocol_engine/commands/command_unions.py +974 -0
  309. opentrons/protocol_engine/commands/comment.py +57 -0
  310. opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
  311. opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
  312. opentrons/protocol_engine/commands/custom.py +67 -0
  313. opentrons/protocol_engine/commands/dispense.py +194 -0
  314. opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
  315. opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
  316. opentrons/protocol_engine/commands/drop_tip.py +232 -0
  317. opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
  318. opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
  319. opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
  320. opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
  321. opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
  322. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
  323. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
  324. opentrons/protocol_engine/commands/flex_stacker/store.py +326 -0
  325. opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
  326. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  327. opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
  328. opentrons/protocol_engine/commands/hash_command_params.py +38 -0
  329. opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
  330. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
  331. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
  332. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
  333. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
  334. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
  335. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
  336. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
  337. opentrons/protocol_engine/commands/home.py +100 -0
  338. opentrons/protocol_engine/commands/identify_module.py +86 -0
  339. opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
  340. opentrons/protocol_engine/commands/liquid_probe.py +464 -0
  341. opentrons/protocol_engine/commands/load_labware.py +210 -0
  342. opentrons/protocol_engine/commands/load_lid.py +154 -0
  343. opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
  344. opentrons/protocol_engine/commands/load_liquid.py +95 -0
  345. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  346. opentrons/protocol_engine/commands/load_module.py +223 -0
  347. opentrons/protocol_engine/commands/load_pipette.py +167 -0
  348. opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
  349. opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
  350. opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
  351. opentrons/protocol_engine/commands/move_labware.py +546 -0
  352. opentrons/protocol_engine/commands/move_relative.py +102 -0
  353. opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
  354. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
  355. opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
  356. opentrons/protocol_engine/commands/move_to_well.py +119 -0
  357. opentrons/protocol_engine/commands/movement_common.py +338 -0
  358. opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
  359. opentrons/protocol_engine/commands/pipetting_common.py +443 -0
  360. opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
  361. opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
  362. opentrons/protocol_engine/commands/reload_labware.py +90 -0
  363. opentrons/protocol_engine/commands/retract_axis.py +75 -0
  364. opentrons/protocol_engine/commands/robot/__init__.py +70 -0
  365. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -0
  366. opentrons/protocol_engine/commands/robot/common.py +18 -0
  367. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  368. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  369. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  370. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +86 -0
  371. opentrons/protocol_engine/commands/save_position.py +109 -0
  372. opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
  373. opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
  374. opentrons/protocol_engine/commands/set_status_bar.py +89 -0
  375. opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
  376. opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
  377. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
  378. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
  379. opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
  380. opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
  381. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
  382. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
  383. opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
  384. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
  385. opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
  386. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
  387. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
  388. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
  389. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
  390. opentrons/protocol_engine/commands/touch_tip.py +189 -0
  391. opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
  392. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
  393. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
  394. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
  395. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
  396. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
  397. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
  398. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
  399. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
  400. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
  401. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
  402. opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
  403. opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
  404. opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
  405. opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
  406. opentrons/protocol_engine/create_protocol_engine.py +193 -0
  407. opentrons/protocol_engine/engine_support.py +28 -0
  408. opentrons/protocol_engine/error_recovery_policy.py +81 -0
  409. opentrons/protocol_engine/errors/__init__.py +191 -0
  410. opentrons/protocol_engine/errors/error_occurrence.py +182 -0
  411. opentrons/protocol_engine/errors/exceptions.py +1308 -0
  412. opentrons/protocol_engine/execution/__init__.py +50 -0
  413. opentrons/protocol_engine/execution/command_executor.py +216 -0
  414. opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
  415. opentrons/protocol_engine/execution/door_watcher.py +119 -0
  416. opentrons/protocol_engine/execution/equipment.py +819 -0
  417. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  418. opentrons/protocol_engine/execution/gantry_mover.py +686 -0
  419. opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
  420. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
  421. opentrons/protocol_engine/execution/labware_movement.py +297 -0
  422. opentrons/protocol_engine/execution/movement.py +349 -0
  423. opentrons/protocol_engine/execution/pipetting.py +607 -0
  424. opentrons/protocol_engine/execution/queue_worker.py +86 -0
  425. opentrons/protocol_engine/execution/rail_lights.py +25 -0
  426. opentrons/protocol_engine/execution/run_control.py +33 -0
  427. opentrons/protocol_engine/execution/status_bar.py +34 -0
  428. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
  429. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
  430. opentrons/protocol_engine/execution/tip_handler.py +550 -0
  431. opentrons/protocol_engine/labware_offset_standardization.py +194 -0
  432. opentrons/protocol_engine/notes/__init__.py +17 -0
  433. opentrons/protocol_engine/notes/notes.py +59 -0
  434. opentrons/protocol_engine/plugins.py +104 -0
  435. opentrons/protocol_engine/protocol_engine.py +683 -0
  436. opentrons/protocol_engine/resources/__init__.py +26 -0
  437. opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
  438. opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
  439. opentrons/protocol_engine/resources/file_provider.py +161 -0
  440. opentrons/protocol_engine/resources/fixture_validation.py +58 -0
  441. opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
  442. opentrons/protocol_engine/resources/labware_validation.py +73 -0
  443. opentrons/protocol_engine/resources/model_utils.py +32 -0
  444. opentrons/protocol_engine/resources/module_data_provider.py +44 -0
  445. opentrons/protocol_engine/resources/ot3_validation.py +21 -0
  446. opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
  447. opentrons/protocol_engine/slot_standardization.py +128 -0
  448. opentrons/protocol_engine/state/__init__.py +1 -0
  449. opentrons/protocol_engine/state/_abstract_store.py +27 -0
  450. opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
  451. opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
  452. opentrons/protocol_engine/state/_move_types.py +83 -0
  453. opentrons/protocol_engine/state/_well_math.py +193 -0
  454. opentrons/protocol_engine/state/addressable_areas.py +699 -0
  455. opentrons/protocol_engine/state/command_history.py +309 -0
  456. opentrons/protocol_engine/state/commands.py +1158 -0
  457. opentrons/protocol_engine/state/config.py +39 -0
  458. opentrons/protocol_engine/state/files.py +57 -0
  459. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  460. opentrons/protocol_engine/state/geometry.py +2359 -0
  461. opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
  462. opentrons/protocol_engine/state/labware.py +1459 -0
  463. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  464. opentrons/protocol_engine/state/liquids.py +73 -0
  465. opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
  466. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
  467. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
  468. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
  469. opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
  470. opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
  471. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
  472. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
  473. opentrons/protocol_engine/state/modules.py +1500 -0
  474. opentrons/protocol_engine/state/motion.py +373 -0
  475. opentrons/protocol_engine/state/pipettes.py +905 -0
  476. opentrons/protocol_engine/state/state.py +421 -0
  477. opentrons/protocol_engine/state/state_summary.py +36 -0
  478. opentrons/protocol_engine/state/tips.py +420 -0
  479. opentrons/protocol_engine/state/update_types.py +904 -0
  480. opentrons/protocol_engine/state/wells.py +290 -0
  481. opentrons/protocol_engine/types/__init__.py +308 -0
  482. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  483. opentrons/protocol_engine/types/command_annotations.py +53 -0
  484. opentrons/protocol_engine/types/deck_configuration.py +81 -0
  485. opentrons/protocol_engine/types/execution.py +96 -0
  486. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  487. opentrons/protocol_engine/types/instrument.py +47 -0
  488. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  489. opentrons/protocol_engine/types/labware.py +131 -0
  490. opentrons/protocol_engine/types/labware_movement.py +22 -0
  491. opentrons/protocol_engine/types/labware_offset_location.py +111 -0
  492. opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
  493. opentrons/protocol_engine/types/liquid.py +40 -0
  494. opentrons/protocol_engine/types/liquid_class.py +59 -0
  495. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  496. opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
  497. opentrons/protocol_engine/types/location.py +194 -0
  498. opentrons/protocol_engine/types/module.py +303 -0
  499. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  500. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  501. opentrons/protocol_engine/types/tip.py +18 -0
  502. opentrons/protocol_engine/types/util.py +21 -0
  503. opentrons/protocol_engine/types/well_position.py +124 -0
  504. opentrons/protocol_reader/__init__.py +37 -0
  505. opentrons/protocol_reader/extract_labware_definitions.py +66 -0
  506. opentrons/protocol_reader/file_format_validator.py +152 -0
  507. opentrons/protocol_reader/file_hasher.py +27 -0
  508. opentrons/protocol_reader/file_identifier.py +284 -0
  509. opentrons/protocol_reader/file_reader_writer.py +90 -0
  510. opentrons/protocol_reader/input_file.py +16 -0
  511. opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
  512. opentrons/protocol_reader/protocol_reader.py +188 -0
  513. opentrons/protocol_reader/protocol_source.py +124 -0
  514. opentrons/protocol_reader/role_analyzer.py +86 -0
  515. opentrons/protocol_runner/__init__.py +26 -0
  516. opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
  517. opentrons/protocol_runner/json_file_reader.py +55 -0
  518. opentrons/protocol_runner/json_translator.py +314 -0
  519. opentrons/protocol_runner/legacy_command_mapper.py +848 -0
  520. opentrons/protocol_runner/legacy_context_plugin.py +116 -0
  521. opentrons/protocol_runner/protocol_runner.py +530 -0
  522. opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
  523. opentrons/protocol_runner/run_orchestrator.py +496 -0
  524. opentrons/protocol_runner/task_queue.py +95 -0
  525. opentrons/protocols/__init__.py +6 -0
  526. opentrons/protocols/advanced_control/__init__.py +0 -0
  527. opentrons/protocols/advanced_control/common.py +38 -0
  528. opentrons/protocols/advanced_control/mix.py +60 -0
  529. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  530. opentrons/protocols/advanced_control/transfers/common.py +180 -0
  531. opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
  532. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
  533. opentrons/protocols/api_support/__init__.py +0 -0
  534. opentrons/protocols/api_support/constants.py +8 -0
  535. opentrons/protocols/api_support/deck_type.py +110 -0
  536. opentrons/protocols/api_support/definitions.py +18 -0
  537. opentrons/protocols/api_support/instrument.py +151 -0
  538. opentrons/protocols/api_support/labware_like.py +233 -0
  539. opentrons/protocols/api_support/tip_tracker.py +175 -0
  540. opentrons/protocols/api_support/types.py +32 -0
  541. opentrons/protocols/api_support/util.py +403 -0
  542. opentrons/protocols/bundle.py +89 -0
  543. opentrons/protocols/duration/__init__.py +4 -0
  544. opentrons/protocols/duration/errors.py +5 -0
  545. opentrons/protocols/duration/estimator.py +628 -0
  546. opentrons/protocols/execution/__init__.py +0 -0
  547. opentrons/protocols/execution/dev_types.py +181 -0
  548. opentrons/protocols/execution/errors.py +40 -0
  549. opentrons/protocols/execution/execute.py +84 -0
  550. opentrons/protocols/execution/execute_json_v3.py +275 -0
  551. opentrons/protocols/execution/execute_json_v4.py +359 -0
  552. opentrons/protocols/execution/execute_json_v5.py +28 -0
  553. opentrons/protocols/execution/execute_python.py +169 -0
  554. opentrons/protocols/execution/json_dispatchers.py +87 -0
  555. opentrons/protocols/execution/types.py +7 -0
  556. opentrons/protocols/geometry/__init__.py +0 -0
  557. opentrons/protocols/geometry/planning.py +297 -0
  558. opentrons/protocols/labware.py +312 -0
  559. opentrons/protocols/models/__init__.py +0 -0
  560. opentrons/protocols/models/json_protocol.py +679 -0
  561. opentrons/protocols/parameters/__init__.py +0 -0
  562. opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
  563. opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
  564. opentrons/protocols/parameters/exceptions.py +34 -0
  565. opentrons/protocols/parameters/parameter_definition.py +272 -0
  566. opentrons/protocols/parameters/types.py +17 -0
  567. opentrons/protocols/parameters/validation.py +267 -0
  568. opentrons/protocols/parse.py +671 -0
  569. opentrons/protocols/types.py +159 -0
  570. opentrons/py.typed +0 -0
  571. opentrons/resources/scripts/lpc21isp +0 -0
  572. opentrons/resources/smoothie-edge-8414642.hex +23010 -0
  573. opentrons/simulate.py +1065 -0
  574. opentrons/system/__init__.py +6 -0
  575. opentrons/system/camera.py +51 -0
  576. opentrons/system/log_control.py +59 -0
  577. opentrons/system/nmcli.py +856 -0
  578. opentrons/system/resin.py +24 -0
  579. opentrons/system/smoothie_update.py +15 -0
  580. opentrons/system/wifi.py +204 -0
  581. opentrons/tools/__init__.py +0 -0
  582. opentrons/tools/args_handler.py +22 -0
  583. opentrons/tools/write_pipette_memory.py +157 -0
  584. opentrons/types.py +618 -0
  585. opentrons/util/__init__.py +1 -0
  586. opentrons/util/async_helpers.py +166 -0
  587. opentrons/util/broker.py +84 -0
  588. opentrons/util/change_notifier.py +47 -0
  589. opentrons/util/entrypoint_util.py +278 -0
  590. opentrons/util/get_union_elements.py +26 -0
  591. opentrons/util/helpers.py +6 -0
  592. opentrons/util/linal.py +178 -0
  593. opentrons/util/logging_config.py +265 -0
  594. opentrons/util/logging_queue_handler.py +61 -0
  595. opentrons/util/performance_helpers.py +157 -0
  596. opentrons-8.6.0a1.dist-info/METADATA +37 -0
  597. opentrons-8.6.0a1.dist-info/RECORD +600 -0
  598. opentrons-8.6.0a1.dist-info/WHEEL +4 -0
  599. opentrons-8.6.0a1.dist-info/entry_points.txt +3 -0
  600. opentrons-8.6.0a1.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,1347 @@
1
+ import asyncio
2
+ import contextlib
3
+ from dataclasses import replace
4
+ from functools import partial
5
+ import logging
6
+ import pathlib
7
+ from collections import OrderedDict
8
+ from typing import (
9
+ Callable,
10
+ Dict,
11
+ Union,
12
+ List,
13
+ Optional,
14
+ Tuple,
15
+ Sequence,
16
+ Set,
17
+ Any,
18
+ TypeVar,
19
+ Mapping,
20
+ cast,
21
+ )
22
+
23
+ from opentrons_shared_data.errors.exceptions import (
24
+ PositionUnknownError,
25
+ UnsupportedHardwareCommand,
26
+ )
27
+ from opentrons_shared_data.pipette import (
28
+ pipette_load_name_conversions as pipette_load_name,
29
+ )
30
+ from opentrons_shared_data.pipette.types import PipetteName
31
+ from opentrons_shared_data.robot.types import RobotType
32
+ from opentrons import types as top_types
33
+ from opentrons.config import robot_configs
34
+ from opentrons.config.types import RobotConfig, OT3Config
35
+ from opentrons.drivers.rpi_drivers.types import USBPort, PortGroup
36
+
37
+ from .util import use_or_initialize_loop, check_motion_bounds, ot2_axis_to_string
38
+ from .instruments.ot2.pipette import (
39
+ generate_hardware_configs,
40
+ load_from_config_and_check_skip,
41
+ )
42
+ from .backends import Controller, Simulator
43
+ from .execution_manager import ExecutionManagerProvider
44
+ from .pause_manager import PauseManager
45
+ from .module_control import AttachedModulesControl
46
+ from .types import (
47
+ Axis,
48
+ CriticalPoint,
49
+ DoorState,
50
+ DoorStateNotification,
51
+ ErrorMessageNotification,
52
+ HardwareEventHandler,
53
+ HardwareAction,
54
+ MotionChecks,
55
+ PauseType,
56
+ StatusBarState,
57
+ EstopState,
58
+ SubSystem,
59
+ SubSystemState,
60
+ HardwareFeatureFlags,
61
+ TipScrapeType,
62
+ )
63
+ from . import modules
64
+ from .robot_calibration import (
65
+ RobotCalibrationProvider,
66
+ RobotCalibration,
67
+ )
68
+ from .protocols import HardwareControlInterface
69
+ from .instruments.ot2.pipette_handler import PipetteHandlerProvider
70
+ from .instruments.ot2.instrument_calibration import load_pipette_offset
71
+ from .motion_utilities import (
72
+ target_position_from_absolute,
73
+ target_position_from_relative,
74
+ target_position_from_plunger,
75
+ deck_from_machine,
76
+ machine_from_deck,
77
+ )
78
+
79
+
80
+ mod_log = logging.getLogger(__name__)
81
+
82
+ AttachedModuleSpec = Dict[str, List[Union[str, Tuple[str, str]]]]
83
+
84
+
85
+ class API(
86
+ ExecutionManagerProvider,
87
+ RobotCalibrationProvider,
88
+ PipetteHandlerProvider[top_types.Mount],
89
+ # This MUST be kept last in the inheritance list so that it is
90
+ # deprioritized in the method resolution order; otherwise, invocations
91
+ # of methods that are present in the protocol will call the (empty,
92
+ # do-nothing) methods in the protocol. This will happily make all the
93
+ # tests fail.
94
+ HardwareControlInterface[RobotCalibration, top_types.Mount, RobotConfig],
95
+ ):
96
+ """This API is the primary interface to the hardware controller.
97
+
98
+ Because the hardware manager controls access to the system's hardware
99
+ as a whole, it is designed as a class of which only one should be
100
+ instantiated at a time. This class's methods should be the only method
101
+ of external access to the hardware. Each method may be minimal - it may
102
+ only delegate the call to another submodule of the hardware manager -
103
+ but its purpose is to be gathered here to provide a single interface.
104
+
105
+ This implements the protocols in opentrons.hardware_control.protocols,
106
+ and longer method docstrings may be found there. Docstrings for the
107
+ methods in this class only note where their behavior is different or
108
+ extended from that described in the protocol.
109
+ """
110
+
111
+ CLS_LOG = mod_log.getChild("API")
112
+
113
+ def __init__(
114
+ self,
115
+ backend: Union[Controller, Simulator],
116
+ loop: asyncio.AbstractEventLoop,
117
+ config: RobotConfig,
118
+ feature_flags: Optional[HardwareFeatureFlags] = None,
119
+ ) -> None:
120
+ """Initialize an API instance.
121
+
122
+ This should rarely be explicitly invoked by an external user; instead,
123
+ one of the factory methods build_hardware_controller or
124
+ build_hardware_simulator should be used.
125
+ """
126
+ self._log = self.CLS_LOG.getChild(str(id(self)))
127
+ self._config = config
128
+ self._backend = backend
129
+ self._loop = loop
130
+ # If no feature flag set is defined, we will use the default values
131
+ self._feature_flags = feature_flags or HardwareFeatureFlags()
132
+
133
+ self._callbacks: Set[HardwareEventHandler] = set()
134
+ # {'X': 0.0, 'Y': 0.0, 'Z': 0.0, 'A': 0.0, 'B': 0.0, 'C': 0.0}
135
+ self._current_position: Dict[Axis, float] = {}
136
+
137
+ self._last_moved_mount: Optional[top_types.Mount] = None
138
+ # The motion lock synchronizes calls to long-running physical tasks
139
+ # involved in motion. This fixes issue where for instance a move()
140
+ # or home() call is in flight and something else calls
141
+ # current_position(), which will not be updated until the move() or
142
+ # home() call succeeds or fails.
143
+ self._motion_lock = asyncio.Lock()
144
+ self._door_state = DoorState.CLOSED
145
+ self._pause_manager = PauseManager()
146
+ ExecutionManagerProvider.__init__(self, isinstance(backend, Simulator))
147
+ RobotCalibrationProvider.__init__(self)
148
+ PipetteHandlerProvider.__init__(
149
+ self, {top_types.Mount.LEFT: None, top_types.Mount.RIGHT: None}
150
+ )
151
+
152
+ @property
153
+ def door_state(self) -> DoorState:
154
+ return self._door_state
155
+
156
+ @door_state.setter
157
+ def door_state(self, door_state: DoorState) -> None:
158
+ self._door_state = door_state
159
+
160
+ def _update_door_state(self, door_state: DoorState) -> None:
161
+ mod_log.info(f"Updating the window switch status: {door_state}")
162
+ self.door_state = door_state
163
+ for cb in self._callbacks:
164
+ hw_event = DoorStateNotification(new_state=door_state)
165
+ try:
166
+ cb(hw_event)
167
+ except Exception:
168
+ mod_log.exception("Errored during door state event callback")
169
+
170
+ def _reset_last_mount(self) -> None:
171
+ self._last_moved_mount = None
172
+
173
+ def get_deck_from_machine(
174
+ self, machine_pos: Dict[Axis, float]
175
+ ) -> Dict[Axis, float]:
176
+ return deck_from_machine(
177
+ machine_pos=machine_pos,
178
+ attitude=self._robot_calibration.deck_calibration.attitude,
179
+ offset=top_types.Point(0, 0, 0),
180
+ robot_type=cast(RobotType, "OT-2 Standard"),
181
+ )
182
+
183
+ @classmethod
184
+ async def build_hardware_controller( # noqa: C901
185
+ cls,
186
+ config: Union[RobotConfig, OT3Config, None] = None,
187
+ port: Optional[str] = None,
188
+ loop: Optional[asyncio.AbstractEventLoop] = None,
189
+ firmware: Optional[Tuple[pathlib.Path, str]] = None,
190
+ feature_flags: Optional[HardwareFeatureFlags] = None,
191
+ ) -> "API":
192
+ """Build a hardware controller that will actually talk to hardware.
193
+
194
+ This method should not be used outside of a real robot, and on a
195
+ real robot only one true hardware controller may be active at one
196
+ time.
197
+
198
+ :param config: A config to preload. If not specified, load the default.
199
+ :param port: A port to connect to. If not specified, the default port
200
+ (found by scanning for connected FT232Rs).
201
+ :param loop: An event loop to use. If not specified, use the result of
202
+ :py:meth:`asyncio.get_event_loop`.
203
+ """
204
+ checked_loop = use_or_initialize_loop(loop)
205
+ if isinstance(config, RobotConfig):
206
+ checked_config = config
207
+ else:
208
+ checked_config = robot_configs.load_ot2()
209
+ backend = await Controller.build(checked_config)
210
+ backend.set_lights(button=None, rails=False)
211
+
212
+ async def blink() -> None:
213
+ while True:
214
+ backend.set_lights(button=True, rails=None)
215
+ await asyncio.sleep(0.5)
216
+ backend.set_lights(button=False, rails=None)
217
+ await asyncio.sleep(0.5)
218
+
219
+ blink_task = checked_loop.create_task(blink())
220
+ try:
221
+ try:
222
+ await backend.connect(port)
223
+ fw_version = backend.fw_version
224
+ except Exception:
225
+ mod_log.exception(
226
+ "Motor driver could not connect, reprogramming if possible"
227
+ )
228
+ fw_version = None
229
+
230
+ if firmware is not None:
231
+ if fw_version != firmware[1]:
232
+ await backend.update_firmware(str(firmware[0]), checked_loop, True)
233
+ await backend.connect(port)
234
+ elif firmware is None and fw_version is None:
235
+ msg = (
236
+ "Motor controller could not be connected and no "
237
+ "firmware was provided for (re)programming"
238
+ )
239
+ mod_log.error(msg)
240
+ raise RuntimeError(msg)
241
+
242
+ api_instance = cls(
243
+ backend,
244
+ loop=checked_loop,
245
+ config=checked_config,
246
+ feature_flags=feature_flags,
247
+ )
248
+ await api_instance.cache_instruments()
249
+ module_controls = await AttachedModulesControl.build(
250
+ api_instance, board_revision=backend.board_revision
251
+ )
252
+ backend.module_controls = module_controls
253
+ checked_loop.create_task(backend.watch(loop=checked_loop))
254
+ backend.start_gpio_door_watcher(
255
+ loop=checked_loop, update_door_state=api_instance._update_door_state
256
+ )
257
+ return api_instance
258
+ finally:
259
+ blink_task.cancel()
260
+ try:
261
+ await blink_task
262
+ except asyncio.CancelledError:
263
+ pass
264
+
265
+ @classmethod
266
+ async def build_hardware_simulator(
267
+ cls,
268
+ attached_instruments: Optional[
269
+ Dict[top_types.Mount, Dict[str, Optional[str]]]
270
+ ] = None,
271
+ attached_modules: Optional[Dict[str, List[modules.SimulatingModule]]] = None,
272
+ config: Optional[Union[RobotConfig, OT3Config]] = None,
273
+ loop: Optional[asyncio.AbstractEventLoop] = None,
274
+ strict_attached_instruments: bool = True,
275
+ feature_flags: Optional[HardwareFeatureFlags] = None,
276
+ ) -> "API":
277
+ """Build a simulating hardware controller.
278
+
279
+ This method may be used both on a real robot and on dev machines.
280
+ Multiple simulating hardware controllers may be active at one time.
281
+ """
282
+
283
+ if None is attached_instruments:
284
+ attached_instruments = {}
285
+
286
+ if None is attached_modules:
287
+ attached_modules = {}
288
+
289
+ checked_loop = use_or_initialize_loop(loop)
290
+ if isinstance(config, RobotConfig):
291
+ checked_config = config
292
+ else:
293
+ checked_config = robot_configs.load_ot2()
294
+ backend = await Simulator.build(
295
+ attached_instruments,
296
+ attached_modules,
297
+ checked_config,
298
+ checked_loop,
299
+ strict_attached_instruments,
300
+ )
301
+ api_instance = cls(
302
+ backend,
303
+ loop=checked_loop,
304
+ config=checked_config,
305
+ feature_flags=feature_flags,
306
+ )
307
+ await api_instance.cache_instruments()
308
+ module_controls = await AttachedModulesControl.build(
309
+ api_instance, board_revision=backend.board_revision
310
+ )
311
+ backend.module_controls = module_controls
312
+ await backend.watch()
313
+ return api_instance
314
+
315
+ def __repr__(self) -> str:
316
+ return "<{} using backend {}>".format(type(self), type(self._backend))
317
+
318
+ async def get_serial_number(self) -> Optional[str]:
319
+ return await self._backend.get_serial_number()
320
+
321
+ @property
322
+ def loop(self) -> asyncio.AbstractEventLoop:
323
+ """The event loop used by this instance."""
324
+ return self._loop
325
+
326
+ @property
327
+ def is_simulator(self) -> bool:
328
+ """`True` if this is a simulator; `False` otherwise."""
329
+ return isinstance(self._backend, Simulator)
330
+
331
+ def register_callback(self, cb: HardwareEventHandler) -> Callable[[], None]:
332
+ """Allows the caller to register a callback, and returns a closure
333
+ that can be used to unregister the provided callback
334
+ """
335
+ self._callbacks.add(cb)
336
+
337
+ def unregister() -> None:
338
+ self._callbacks.remove(cb)
339
+
340
+ return unregister
341
+
342
+ def get_fw_version(self) -> str:
343
+ """
344
+ Return the firmware version of the connected motor control board.
345
+
346
+ The version is a string retrieved directly from the attached hardware
347
+ (or possibly simulator).
348
+ """
349
+ from_backend = self._backend.fw_version
350
+ if from_backend is None:
351
+ return "unknown"
352
+ else:
353
+ return from_backend
354
+
355
+ @property
356
+ def fw_version(self) -> str:
357
+ return self.get_fw_version()
358
+
359
+ @property
360
+ def board_revision(self) -> str:
361
+ return str(self._backend.board_revision)
362
+
363
+ @property
364
+ def attached_subsystems(self) -> Dict[SubSystem, SubSystemState]:
365
+ return {}
366
+
367
+ # Incidentals (i.e. not motion) API
368
+
369
+ async def set_lights(
370
+ self, button: Optional[bool] = None, rails: Optional[bool] = None
371
+ ) -> None:
372
+ """Control the robot lights."""
373
+ self._backend.set_lights(button, rails)
374
+
375
+ async def get_lights(self) -> Dict[str, bool]:
376
+ """Return the current status of the robot lights.
377
+
378
+ :returns: A dict of the lights: `{'button': bool, 'rails': bool}`
379
+ """
380
+ return self._backend.get_lights()
381
+
382
+ async def identify(self, duration_s: int = 5) -> None:
383
+ """Blink the button light to identify the robot."""
384
+ count = duration_s * 4
385
+ on = False
386
+ for sec in range(count):
387
+ then = self._loop.time()
388
+ await self.set_lights(button=on)
389
+ on = not on
390
+ now = self._loop.time()
391
+ await asyncio.sleep(max(0, 0.25 - (now - then)))
392
+ await self.set_lights(button=True)
393
+
394
+ async def set_status_bar_state(self, state: StatusBarState) -> None:
395
+ """The status bar does not exist on OT-2!"""
396
+ return None
397
+
398
+ async def set_status_bar_enabled(self, enabled: bool) -> None:
399
+ """The status bar does not exist on OT-2!"""
400
+ return None
401
+
402
+ def get_status_bar_enabled(self) -> bool:
403
+ """There is no status bar on OT-2, return False at all times."""
404
+ return False
405
+
406
+ def get_status_bar_state(self) -> StatusBarState:
407
+ """There is no status bar on OT-2, return IDLE at all times."""
408
+ return StatusBarState.IDLE
409
+
410
+ @ExecutionManagerProvider.wait_for_running
411
+ async def delay(self, duration_s: float) -> None:
412
+ """Delay execution by pausing and sleeping."""
413
+ self.pause(PauseType.DELAY)
414
+ try:
415
+ await self.do_delay(duration_s)
416
+ finally:
417
+ self.resume(PauseType.DELAY)
418
+
419
+ @property
420
+ def attached_modules(self) -> List[modules.AbstractModule]:
421
+ return self._backend.module_controls.available_modules
422
+
423
+ async def update_firmware(
424
+ self,
425
+ firmware_file: str,
426
+ loop: Optional[asyncio.AbstractEventLoop] = None,
427
+ explicit_modeset: bool = True,
428
+ ) -> str:
429
+ """Update the firmware on the motor controller board.
430
+
431
+ :param firmware_file: The path to the firmware file.
432
+ :param explicit_modeset: `True` to force the smoothie into programming
433
+ mode; `False` to assume it is already in
434
+ programming mode.
435
+ :param loop: An asyncio event loop to use; if not specified, the one
436
+ associated with this instance will be used.
437
+ :returns: The stdout of the tool used to update the smoothie
438
+ """
439
+ if None is loop:
440
+ checked_loop = self._loop
441
+ else:
442
+ checked_loop = loop
443
+ return await self._backend.update_firmware(
444
+ firmware_file, checked_loop, explicit_modeset
445
+ )
446
+
447
+ def has_gripper(self) -> bool:
448
+ return False
449
+
450
+ async def cache_instruments(
451
+ self,
452
+ require: Optional[Dict[top_types.Mount, PipetteName]] = None,
453
+ skip_if_would_block: bool = False,
454
+ ) -> None:
455
+ """
456
+ Scan the attached instruments, take necessary configuration actions,
457
+ and set up hardware controller internal state if necessary.
458
+ """
459
+ self._log.info("Updating instrument model cache")
460
+ checked_require = require or {}
461
+ for mount, name in checked_require.items():
462
+ if not pipette_load_name.supported_pipette(name):
463
+ raise RuntimeError(f"{name} is not a valid pipette name")
464
+ async with self._motion_lock:
465
+ found = await self._backend.get_attached_instruments(checked_require)
466
+ for mount, instrument_data in found.items():
467
+ config = instrument_data.get("config")
468
+ req_instr = checked_require.get(mount, None)
469
+ pip_id = instrument_data.get("id")
470
+ pip_offset_cal = load_pipette_offset(pip_id, mount)
471
+ p, may_skip = load_from_config_and_check_skip(
472
+ config,
473
+ self._attached_instruments[mount],
474
+ req_instr,
475
+ pip_id,
476
+ pip_offset_cal,
477
+ self._feature_flags.use_old_aspiration_functions,
478
+ )
479
+ self._attached_instruments[mount] = p
480
+ if req_instr and p:
481
+ converted_name = pipette_load_name.convert_to_pipette_name_type(
482
+ req_instr
483
+ )
484
+ p.act_as(converted_name)
485
+
486
+ if may_skip:
487
+ self._log.info(f"Skipping configuration on {mount.name}")
488
+ continue
489
+
490
+ self._log.info(f"Doing full configuration on {mount.name}")
491
+ hw_config = generate_hardware_configs(
492
+ p, self._config, self._backend.board_revision
493
+ )
494
+ await self._backend.configure_mount(mount, hw_config)
495
+ self._log.info("Instruments found: {}".format(self._attached_instruments))
496
+
497
+ # Global actions API
498
+ def pause(self, pause_type: PauseType) -> None:
499
+ """
500
+ Pause motion of the robot after a current motion concludes.
501
+ """
502
+ self._pause_manager.pause(pause_type)
503
+
504
+ async def _chained_calls() -> None:
505
+ await self._execution_manager.pause()
506
+ self._backend.pause()
507
+
508
+ asyncio.run_coroutine_threadsafe(_chained_calls(), self._loop)
509
+
510
+ def pause_with_message(self, message: str) -> None:
511
+ """As pause, but providing a message to registered callbacks."""
512
+ self._log.warning(f"Pause with message: {message}")
513
+ notification = ErrorMessageNotification(message=message)
514
+ for cb in self._callbacks:
515
+ cb(notification)
516
+ self.pause(PauseType.PAUSE)
517
+
518
+ def resume(self, pause_type: PauseType) -> None:
519
+ """Resume motion after a call to pause."""
520
+ self._pause_manager.resume(pause_type)
521
+
522
+ if self._pause_manager.should_pause:
523
+ return
524
+
525
+ # Resume must be called immediately to awaken thread running hardware
526
+ # methods (ThreadManager)
527
+ self._backend.resume()
528
+
529
+ async def _chained_calls() -> None:
530
+ # mirror what happens API.pause.
531
+ await self._execution_manager.resume()
532
+ self._backend.resume()
533
+
534
+ asyncio.run_coroutine_threadsafe(_chained_calls(), self._loop)
535
+
536
+ async def halt(self, disengage_before_stopping: bool = False) -> None:
537
+ """Immediately stop motion, cancel execution manager and cancel running tasks.
538
+
539
+ After this call, the smoothie will be in a bad state until a call to
540
+ :py:meth:`stop`.
541
+ """
542
+ if disengage_before_stopping:
543
+ await self._backend.hard_halt()
544
+ await self._backend.halt()
545
+
546
+ async def stop(self, home_after: bool = True) -> None:
547
+ """
548
+ Stop motion as soon as possible, reset, and optionally home.
549
+
550
+ This will cancel motion (after the current call to :py:meth:`move`;
551
+ see :py:meth:`pause` for more detail), then home and reset the
552
+ robot. After this call, no further recovery is necessary.
553
+ """
554
+ await self._backend.halt() # calls smoothie_driver.kill()
555
+ await self.cancel_execution_and_running_tasks()
556
+ self._log.info("Recovering from halt")
557
+ await self.reset()
558
+ await self.cache_instruments()
559
+
560
+ if home_after:
561
+ await self.home()
562
+
563
+ def is_movement_execution_taskified(self) -> bool:
564
+ return self.taskify_movement_execution
565
+
566
+ def should_taskify_movement_execution(self, taskify: bool) -> None:
567
+ self.taskify_movement_execution = taskify
568
+
569
+ async def cancel_execution_and_running_tasks(self) -> None:
570
+ await self._execution_manager.cancel()
571
+
572
+ async def reset(self) -> None:
573
+ """Reset the stored state of the system."""
574
+ self._pause_manager.reset()
575
+ await self._execution_manager.reset()
576
+ await PipetteHandlerProvider.reset(self)
577
+
578
+ # Gantry/frame (i.e. not pipette) action API
579
+ async def home_z(
580
+ self,
581
+ mount: Optional[top_types.Mount] = None,
582
+ allow_home_other: bool = True,
583
+ ) -> None:
584
+ """Home the Z-stage(s) of the instrument mounts.
585
+
586
+ If given a mount, will try to only home that mount.
587
+ However, if the other mount is currently extended,
588
+ both mounts will be homed, unless `allow_home_other`
589
+ is explicitly set to `False`.
590
+
591
+ Setting `allow_home_other` to `False` is a bad idea,
592
+ but the option exists for strict backwards compatibility.
593
+ """
594
+ if mount is not None and (
595
+ self._last_moved_mount in [mount, None] or allow_home_other is False
596
+ ):
597
+ axes = [Axis.by_mount(mount)]
598
+ else:
599
+ axes = [Axis.Z, Axis.A]
600
+
601
+ await self.home(axes)
602
+
603
+ async def _do_plunger_home(
604
+ self,
605
+ axis: Optional[Axis] = None,
606
+ mount: Optional[top_types.Mount] = None,
607
+ acquire_lock: bool = True,
608
+ ) -> None:
609
+ assert (axis is not None) ^ (mount is not None), "specify either axis or mount"
610
+ if axis:
611
+ checked_axis = axis
612
+ checked_mount = Axis.to_ot2_mount(checked_axis)
613
+ if mount:
614
+ checked_mount = mount
615
+ checked_axis = Axis.of_plunger(checked_mount)
616
+ instr = self.hardware_instruments[checked_mount]
617
+ if not instr:
618
+ return
619
+ async with contextlib.AsyncExitStack() as stack:
620
+ if acquire_lock:
621
+ await stack.enter_async_context(self._motion_lock)
622
+ with self._backend.save_current():
623
+ self._backend.set_active_current(
624
+ {checked_axis: instr.plunger_motor_current.run}
625
+ )
626
+ await self._backend.home([ot2_axis_to_string(checked_axis)])
627
+ # either we were passed False for our acquire_lock and we
628
+ # should pass it on, or we acquired the lock above and
629
+ # shouldn't do it again
630
+ target_pos = target_position_from_plunger(
631
+ checked_mount,
632
+ instr.plunger_positions.bottom,
633
+ self._current_position,
634
+ )
635
+ await self._move(
636
+ target_pos,
637
+ acquire_lock=False,
638
+ home_flagged_axes=False,
639
+ )
640
+
641
+ @ExecutionManagerProvider.wait_for_running
642
+ async def home_plunger(self, mount: top_types.Mount) -> None:
643
+ """
644
+ Home the plunger motor for a mount, and then return it to the 'bottom'
645
+ position.
646
+ """
647
+ await self.current_position(mount=mount, refresh=True)
648
+ await self._do_plunger_home(mount=mount, acquire_lock=True)
649
+
650
+ @ExecutionManagerProvider.wait_for_running
651
+ async def home(self, axes: Optional[List[Axis]] = None) -> None:
652
+ """Home the entire robot and initialize current position."""
653
+ # Should we assert/ raise an error or just remove non-ot2 axes and log warning?
654
+ # No internal code passes OT3 axes as arguments on an OT2. But a user/ client
655
+ # can still explicitly specify an OT3 axis even when working on an OT2.
656
+ # Adding this check in order to prevent misuse of axes types.
657
+ if axes:
658
+ unsupported = list(axis not in Axis.ot2_axes() for axis in axes)
659
+ if any(unsupported):
660
+ raise UnsupportedHardwareCommand(
661
+ message=f"At least one axis in {axes} is not supported on the OT2.",
662
+ detail={"unsupported_axes": str(unsupported)},
663
+ )
664
+ self._reset_last_mount()
665
+ # Initialize/update current_position
666
+ checked_axes = axes or [ax for ax in Axis.ot2_axes()]
667
+ gantry = [ax for ax in checked_axes if ax in Axis.gantry_axes()]
668
+ smoothie_gantry = [ot2_axis_to_string(ax) for ax in gantry]
669
+ smoothie_pos = {}
670
+ plungers = [ax for ax in checked_axes if ax not in Axis.gantry_axes()]
671
+
672
+ async with self._motion_lock:
673
+ if smoothie_gantry:
674
+ smoothie_pos.update(await self._backend.home(smoothie_gantry))
675
+ self._current_position = self.get_deck_from_machine(
676
+ self._axis_map_from_string_map(smoothie_pos)
677
+ )
678
+ for plunger in plungers:
679
+ await self._do_plunger_home(axis=plunger, acquire_lock=False)
680
+
681
+ async def current_position(
682
+ self,
683
+ mount: top_types.Mount,
684
+ critical_point: Optional[CriticalPoint] = None,
685
+ refresh: bool = False,
686
+ # TODO(mc, 2021-11-15): combine with `refresh` for more reliable
687
+ # position reporting when motors are not homed
688
+ fail_on_not_homed: bool = False,
689
+ ) -> Dict[Axis, float]:
690
+ """Return the postion (in deck coords) of the critical point of the
691
+ specified mount.
692
+ """
693
+ z_ax = Axis.by_mount(mount)
694
+ plunger_ax = Axis.of_plunger(mount)
695
+ position_axes = [Axis.X, Axis.Y, z_ax, plunger_ax]
696
+
697
+ if fail_on_not_homed:
698
+ if not self._current_position:
699
+ raise PositionUnknownError(
700
+ message=f"Current position of {str(mount)} pipette is unknown,"
701
+ " please home.",
702
+ detail={"mount": str(mount), "missing_axes": str(position_axes)},
703
+ )
704
+ axes_str = [ot2_axis_to_string(a) for a in position_axes]
705
+ if not self._backend.is_homed(axes_str):
706
+ unhomed = self._backend._unhomed_axes(axes_str)
707
+ raise PositionUnknownError(
708
+ message=f"{str(mount)} pipette axes ({unhomed}) must be homed.",
709
+ detail={"mount": str(mount), "unhomed_axes": str(unhomed)},
710
+ )
711
+ elif not self._current_position and not refresh:
712
+ raise PositionUnknownError(
713
+ message="Current position is unknown; please home motors."
714
+ )
715
+ async with self._motion_lock:
716
+ if refresh:
717
+ smoothie_pos = await self._backend.update_position()
718
+ self._current_position = self.get_deck_from_machine(
719
+ self._axis_map_from_string_map(smoothie_pos)
720
+ )
721
+ if mount == top_types.Mount.RIGHT:
722
+ offset = top_types.Point(0, 0, 0)
723
+ else:
724
+ offset = top_types.Point(*self._config.left_mount_offset)
725
+
726
+ cp = self.critical_point_for(mount, critical_point)
727
+ return {
728
+ Axis.X: self._current_position[Axis.X] + offset[0] + cp.x,
729
+ Axis.Y: self._current_position[Axis.Y] + offset[1] + cp.y,
730
+ z_ax: self._current_position[z_ax] + offset[2] + cp.z,
731
+ plunger_ax: self._current_position[plunger_ax],
732
+ }
733
+
734
+ async def gantry_position(
735
+ self,
736
+ mount: top_types.Mount,
737
+ critical_point: Optional[CriticalPoint] = None,
738
+ refresh: bool = False,
739
+ # TODO(mc, 2021-11-15): combine with `refresh` for more reliable
740
+ # position reporting when motors are not homed
741
+ fail_on_not_homed: bool = False,
742
+ ) -> top_types.Point:
743
+ """Return the position of the critical point for only gantry axes."""
744
+ cur_pos = await self.current_position(
745
+ mount,
746
+ critical_point,
747
+ refresh,
748
+ fail_on_not_homed,
749
+ )
750
+ return top_types.Point(
751
+ x=cur_pos[Axis.X], y=cur_pos[Axis.Y], z=cur_pos[Axis.by_mount(mount)]
752
+ )
753
+
754
+ # TODO(mc, 2022-05-13): return resulting gantry position
755
+ async def move_to(
756
+ self,
757
+ mount: top_types.Mount,
758
+ abs_position: top_types.Point,
759
+ speed: Optional[float] = None,
760
+ critical_point: Optional[CriticalPoint] = None,
761
+ max_speeds: Optional[Dict[Axis, float]] = None,
762
+ ) -> None:
763
+ """
764
+ Move the critical point of the specified mount to a location
765
+ relative to the deck, at the specified speed.
766
+ """
767
+ if not self._current_position:
768
+ await self.home()
769
+
770
+ target_position = target_position_from_absolute(
771
+ mount,
772
+ abs_position,
773
+ partial(self.critical_point_for, cp_override=critical_point),
774
+ top_types.Point(*self._config.left_mount_offset),
775
+ top_types.Point(0, 0, 0),
776
+ )
777
+
778
+ await self.prepare_for_mount_movement(mount)
779
+ await self._move(target_position, speed=speed, max_speeds=max_speeds)
780
+
781
+ async def move_axes(
782
+ self,
783
+ position: Mapping[Axis, float],
784
+ speed: Optional[float] = None,
785
+ max_speeds: Optional[Dict[Axis, float]] = None,
786
+ expect_stalls: bool = False,
787
+ ) -> None:
788
+ """Moves the effectors of the specified axis to the specified position.
789
+ The effector of the x,y axis is the center of the carriage.
790
+ The effector of the pipette mount axis are the mount critical points but only in z.
791
+ """
792
+ raise UnsupportedHardwareCommand(
793
+ message="move_axes is not supported on the OT-2.",
794
+ detail={"axes_commanded": str(list(position.keys()))},
795
+ )
796
+
797
+ async def move_rel(
798
+ self,
799
+ mount: top_types.Mount,
800
+ delta: top_types.Point,
801
+ speed: Optional[float] = None,
802
+ max_speeds: Optional[Dict[Axis, float]] = None,
803
+ check_bounds: MotionChecks = MotionChecks.NONE,
804
+ fail_on_not_homed: bool = False,
805
+ ) -> None:
806
+ """Move the critical point of the specified mount by a specified
807
+ displacement in a specified direction, at the specified speed.
808
+ """
809
+
810
+ # TODO: Remove the fail_on_not_homed and make this the behavior all the time.
811
+ # Having the optional arg makes the bug stick around in existing code and we
812
+ # really want to fix it when we're not gearing up for a release.
813
+ if not self._current_position:
814
+ if fail_on_not_homed:
815
+ raise PositionUnknownError(
816
+ message="Cannot make a relative move because absolute position"
817
+ " is unknown.",
818
+ detail={
819
+ "mount": str(mount),
820
+ "fail_on_not_homed": str(fail_on_not_homed),
821
+ },
822
+ )
823
+ else:
824
+ await self.home()
825
+
826
+ target_position = target_position_from_relative(
827
+ mount, delta, self._current_position
828
+ )
829
+
830
+ axes_moving = [Axis.X, Axis.Y, Axis.by_mount(mount)]
831
+ axes_str = [ot2_axis_to_string(a) for a in axes_moving]
832
+ if fail_on_not_homed and not self._backend.is_homed(axes_str):
833
+ unhomed = self._backend._unhomed_axes(axes_str)
834
+ raise PositionUnknownError(
835
+ message=f"{str(mount)} pipette axes ({unhomed}) must be homed.",
836
+ detail={"mount": str(mount), "unhomed_axes": str(unhomed)},
837
+ )
838
+
839
+ await self.prepare_for_mount_movement(mount)
840
+ await self._move(
841
+ target_position,
842
+ speed=speed,
843
+ max_speeds=max_speeds,
844
+ check_bounds=check_bounds,
845
+ )
846
+
847
+ async def _cache_and_maybe_retract_mount(self, mount: top_types.Mount) -> None:
848
+ """Retract the 'other' mount if necessary
849
+
850
+ If `mount` does not match the value in :py:attr:`_last_moved_mount`
851
+ (and :py:attr:`_last_moved_mount` exists) then retract the mount
852
+ in :py:attr:`_last_moved_mount`. Also unconditionally update
853
+ :py:attr:`_last_moved_mount` to contain `mount`.
854
+ """
855
+ if mount != self._last_moved_mount and self._last_moved_mount:
856
+ await self.retract(self._last_moved_mount, 10)
857
+ self._last_moved_mount = mount
858
+
859
+ async def prepare_for_mount_movement(self, mount: top_types.Mount) -> None:
860
+ await self._cache_and_maybe_retract_mount(mount)
861
+
862
+ @ExecutionManagerProvider.wait_for_running
863
+ async def _move(
864
+ self,
865
+ target_position: "OrderedDict[Axis, float]",
866
+ speed: Optional[float] = None,
867
+ home_flagged_axes: bool = True,
868
+ max_speeds: Optional[Dict[Axis, float]] = None,
869
+ acquire_lock: bool = True,
870
+ check_bounds: MotionChecks = MotionChecks.NONE,
871
+ ) -> None:
872
+ """Worker function to apply robot motion.
873
+
874
+ Robot motion means the kind of motions that are relevant to the robot,
875
+ i.e. only one pipette plunger and mount move at the same time, and an
876
+ XYZ move in the coordinate frame of one of the pipettes.
877
+
878
+ ``target_position`` should be an ordered dict (ordered by XYZABC)
879
+ of deck calibrated values, containing any specified XY motion and
880
+ at most one of a ZA or BC components. The frame in which to move
881
+ is identified by the presence of (ZA) or (BC).
882
+ """
883
+ machine_pos = self._string_map_from_axis_map(
884
+ machine_from_deck(
885
+ deck_pos=target_position,
886
+ attitude=self._robot_calibration.deck_calibration.attitude,
887
+ offset=top_types.Point(0, 0, 0),
888
+ robot_type=cast(RobotType, "OT-2 Standard"),
889
+ )
890
+ )
891
+
892
+ bounds = self._backend.axis_bounds
893
+ to_check = {
894
+ ax: machine_pos[ot2_axis_to_string(ax)]
895
+ for idx, ax in enumerate(target_position.keys())
896
+ if ax in Axis.gantry_axes()
897
+ }
898
+
899
+ check_motion_bounds(to_check, target_position, bounds, check_bounds)
900
+ checked_maxes = max_speeds or {}
901
+ str_maxes = {ot2_axis_to_string(ax): val for ax, val in checked_maxes.items()}
902
+ async with contextlib.AsyncExitStack() as stack:
903
+ if acquire_lock:
904
+ await stack.enter_async_context(self._motion_lock)
905
+ try:
906
+ await self._backend.move(
907
+ machine_pos,
908
+ speed=speed,
909
+ home_flagged_axes=home_flagged_axes,
910
+ axis_max_speeds=str_maxes,
911
+ )
912
+ except Exception:
913
+ self._log.exception("Move failed")
914
+ self._current_position.clear()
915
+ raise
916
+ else:
917
+ self._current_position.update(target_position)
918
+
919
+ def get_engaged_axes(self) -> Dict[Axis, bool]:
920
+ """Which axes are engaged and holding."""
921
+ return {Axis[ax]: eng for ax, eng in self._backend.engaged_axes().items()}
922
+
923
+ @property
924
+ def engaged_axes(self) -> Dict[Axis, bool]:
925
+ return self.get_engaged_axes()
926
+
927
+ async def disengage_axes(self, which: List[Axis]) -> None:
928
+ await self._backend.disengage_axes([ot2_axis_to_string(ax) for ax in which])
929
+
930
+ def axis_is_present(self, axis: Axis) -> bool:
931
+ is_ot2 = axis in Axis.ot2_axes()
932
+ if not is_ot2:
933
+ return False
934
+ if axis in Axis.pipette_axes():
935
+ mount = Axis.to_ot2_mount(axis)
936
+ if self.attached_pipettes.get(mount) is None:
937
+ return False
938
+ return True
939
+
940
+ @ExecutionManagerProvider.wait_for_running
941
+ async def _fast_home(self, axes: Sequence[str], margin: float) -> Dict[str, float]:
942
+ converted_axes = "".join(axes)
943
+ return await self._backend.fast_home(converted_axes, margin)
944
+
945
+ async def retract(self, mount: top_types.Mount, margin: float = 10) -> None:
946
+ """Pull the specified mount up to its home position.
947
+
948
+ Works regardless of critical point or home status.
949
+ """
950
+ await self.retract_axis(Axis.by_mount(mount), margin)
951
+
952
+ async def retract_axis(self, axis: Axis, margin: float = 10) -> None:
953
+ """Pull the specified axis up to its home position.
954
+
955
+ Works regardless of critical point or home status.
956
+ """
957
+ smoothie_ax = (ot2_axis_to_string(axis),)
958
+
959
+ async with self._motion_lock:
960
+ smoothie_pos = await self._fast_home(smoothie_ax, margin)
961
+ self._current_position = self.get_deck_from_machine(
962
+ self._axis_map_from_string_map(smoothie_pos)
963
+ )
964
+
965
+ # Gantry/frame (i.e. not pipette) config API
966
+ @property
967
+ def config(self) -> RobotConfig:
968
+ """Get the robot's configuration object.
969
+
970
+ :returns .RobotConfig: The object.
971
+ """
972
+ return self._config
973
+
974
+ @config.setter
975
+ def config(self, config: Union[RobotConfig, OT3Config]) -> None:
976
+ """Replace the currently-loaded config"""
977
+ if isinstance(config, RobotConfig):
978
+ self._config = config
979
+ else:
980
+ self._log.error("Cannot use an OT-3 config on an OT-2")
981
+
982
+ def get_config(self) -> RobotConfig:
983
+ """
984
+ Get the robot's configuration object.
985
+
986
+ :returns .RobotConfig: The object.
987
+ """
988
+ return self.config
989
+
990
+ def set_config(self, config: Union[OT3Config, RobotConfig]) -> None:
991
+ """Replace the currently-loaded config"""
992
+ if isinstance(config, RobotConfig):
993
+ self.config = config
994
+ else:
995
+ self._log.error("Cannot use an OT-3 config on an OT-2")
996
+
997
+ async def update_config(self, **kwargs: Any) -> None:
998
+ """Update values of the robot's configuration.
999
+
1000
+ `kwargs` should contain keys of the robot's configuration. For
1001
+ instance, `update_config(log_level='debug)` would change the API
1002
+ server log level to :py:attr:`logging.DEBUG`.
1003
+
1004
+ Documentation on keys can be found in the documentation for
1005
+ :py:class:`.RobotConfig`.
1006
+ """
1007
+ self._config = replace(self._config, **kwargs)
1008
+
1009
+ @property
1010
+ def hardware_feature_flags(self) -> HardwareFeatureFlags:
1011
+ return self._feature_flags
1012
+
1013
+ @hardware_feature_flags.setter
1014
+ def hardware_feature_flags(self, feature_flags: HardwareFeatureFlags) -> None:
1015
+ self._feature_flags = feature_flags
1016
+
1017
+ async def update_deck_calibration(self, new_transform: RobotCalibration) -> None:
1018
+ pass
1019
+
1020
+ # Pipette action API
1021
+ async def prepare_for_aspirate(
1022
+ self, mount: top_types.Mount, rate: float = 1.0
1023
+ ) -> None:
1024
+ """
1025
+ Prepare the pipette for aspiration.
1026
+ """
1027
+ instrument = self.get_pipette(mount)
1028
+ self.ready_for_tip_action(instrument, HardwareAction.PREPARE_ASPIRATE, mount)
1029
+
1030
+ if instrument.current_volume == 0:
1031
+ speed = self.plunger_speed(
1032
+ instrument, instrument.blow_out_flow_rate, "aspirate"
1033
+ )
1034
+ bottom = instrument.plunger_positions.bottom
1035
+ target = target_position_from_plunger(mount, bottom, self._current_position)
1036
+ await self._move(
1037
+ target,
1038
+ speed=(speed * rate),
1039
+ home_flagged_axes=False,
1040
+ )
1041
+ instrument.ready_to_aspirate = True
1042
+
1043
+ async def aspirate(
1044
+ self,
1045
+ mount: top_types.Mount,
1046
+ volume: Optional[float] = None,
1047
+ rate: float = 1.0,
1048
+ correction_volume: float = 0.0,
1049
+ ) -> None:
1050
+ """
1051
+ Aspirate a volume of liquid (in microliters/uL) using this pipette.
1052
+ """
1053
+ aspirate_spec = self.plan_check_aspirate(mount, volume, rate)
1054
+ if not aspirate_spec:
1055
+ return
1056
+ target_pos = target_position_from_plunger(
1057
+ mount,
1058
+ aspirate_spec.plunger_distance,
1059
+ self._current_position,
1060
+ )
1061
+ try:
1062
+ self._backend.set_active_current(
1063
+ {aspirate_spec.axis: aspirate_spec.current}
1064
+ )
1065
+ await self._move(
1066
+ target_pos,
1067
+ speed=aspirate_spec.speed,
1068
+ home_flagged_axes=False,
1069
+ )
1070
+ except Exception:
1071
+ self._log.exception("Aspirate failed")
1072
+ aspirate_spec.instr.set_current_volume(0)
1073
+ raise
1074
+ else:
1075
+ aspirate_spec.instr.add_current_volume(aspirate_spec.volume)
1076
+
1077
+ async def dispense(
1078
+ self,
1079
+ mount: top_types.Mount,
1080
+ volume: Optional[float] = None,
1081
+ rate: float = 1.0,
1082
+ push_out: Optional[float] = None,
1083
+ correction_volume: float = 0.0,
1084
+ is_full_dispense: bool = False,
1085
+ ) -> None:
1086
+ """
1087
+ Dispense a volume of liquid in microliters(uL) using this pipette.
1088
+ """
1089
+
1090
+ dispense_spec = self.plan_check_dispense(mount, volume, rate, push_out)
1091
+ if not dispense_spec:
1092
+ return
1093
+ target_pos = target_position_from_plunger(
1094
+ mount,
1095
+ dispense_spec.plunger_distance,
1096
+ self._current_position,
1097
+ )
1098
+
1099
+ try:
1100
+ self._backend.set_active_current(
1101
+ {dispense_spec.axis: dispense_spec.current}
1102
+ )
1103
+ await self._move(
1104
+ target_pos,
1105
+ speed=dispense_spec.speed,
1106
+ home_flagged_axes=False,
1107
+ )
1108
+ except Exception:
1109
+ self._log.exception("Dispense failed")
1110
+ dispense_spec.instr.set_current_volume(0)
1111
+ raise
1112
+ else:
1113
+ dispense_spec.instr.remove_current_volume(dispense_spec.volume)
1114
+
1115
+ async def blow_out(
1116
+ self, mount: top_types.Mount, volume: Optional[float] = None
1117
+ ) -> None:
1118
+ """
1119
+ Force any remaining liquid to dispense. The liquid will be dispensed at
1120
+ the current location of pipette
1121
+ """
1122
+ blowout_spec = self.plan_check_blow_out(mount)
1123
+ self._backend.set_active_current({blowout_spec.axis: blowout_spec.current})
1124
+ target_pos = target_position_from_plunger(
1125
+ mount,
1126
+ blowout_spec.plunger_distance,
1127
+ self._current_position,
1128
+ )
1129
+
1130
+ try:
1131
+ await self._move(
1132
+ target_pos,
1133
+ speed=blowout_spec.speed,
1134
+ home_flagged_axes=False,
1135
+ )
1136
+ except Exception:
1137
+ self._log.exception("Blow out failed")
1138
+ raise
1139
+ finally:
1140
+ blowout_spec.instr.set_current_volume(0)
1141
+ blowout_spec.instr.ready_to_aspirate = False
1142
+
1143
+ async def update_nozzle_configuration_for_mount(
1144
+ self,
1145
+ mount: top_types.Mount,
1146
+ back_left_nozzle: Optional[str],
1147
+ front_right_nozzle: Optional[str],
1148
+ starting_nozzle: Optional[str] = None,
1149
+ ) -> None:
1150
+ """
1151
+ Update a nozzle configuration for a given pipette.
1152
+
1153
+ The expectation of this function is that the back_left_nozzle/front_right_nozzle are the two corners
1154
+ of a rectangle of nozzles. A call to this function that does not follow that schema will result
1155
+ in an error.
1156
+
1157
+ :param mount: A robot mount that the instrument is on.
1158
+ :param back_left_nozzle: A string representing a nozzle name of the form <LETTER><NUMBER> such as 'A1'.
1159
+ :param front_right_nozzle: A string representing a nozzle name of the form <LETTER><NUMBER> such as 'A1'.
1160
+ :param starting_nozzle: A string representing the starting nozzle which will be used as the critical point
1161
+ of the pipette nozzle configuration. By default, the back left nozzle will be the starting nozzle if
1162
+ none is provided.
1163
+ :return: None.
1164
+
1165
+ If none of the nozzle parameters are provided, the nozzle configuration will be reset to default.
1166
+ """
1167
+ if not back_left_nozzle and not front_right_nozzle and not starting_nozzle:
1168
+ await self.reset_nozzle_configuration(mount)
1169
+ else:
1170
+ assert back_left_nozzle and front_right_nozzle
1171
+ await self.update_nozzle_configuration(
1172
+ mount, back_left_nozzle, front_right_nozzle, starting_nozzle
1173
+ )
1174
+
1175
+ async def tip_pickup_moves(
1176
+ self,
1177
+ mount: top_types.Mount,
1178
+ presses: Optional[int] = None,
1179
+ increment: Optional[float] = None,
1180
+ ) -> None:
1181
+ spec, _ = self.plan_check_pick_up_tip(
1182
+ mount=mount, presses=presses, increment=increment
1183
+ )
1184
+ self._backend.set_active_current(spec.plunger_currents)
1185
+ target_absolute = target_position_from_plunger(
1186
+ mount, spec.plunger_prep_pos, self._current_position
1187
+ )
1188
+ await self._move(
1189
+ target_absolute,
1190
+ home_flagged_axes=False,
1191
+ )
1192
+
1193
+ for press in spec.presses:
1194
+ with self._backend.save_current():
1195
+ self._backend.set_active_current(press.current)
1196
+ target_down = target_position_from_relative(
1197
+ mount, press.relative_down, self._current_position
1198
+ )
1199
+ await self._move(target_down, speed=press.speed)
1200
+ target_up = target_position_from_relative(
1201
+ mount, press.relative_up, self._current_position
1202
+ )
1203
+ await self._move(target_up)
1204
+ # neighboring tips tend to get stuck in the space between
1205
+ # the volume chamber and the drop-tip sleeve on p1000.
1206
+ # This extra shake ensures those tips are removed
1207
+ for rel_point, speed in spec.shake_off_list:
1208
+ await self.move_rel(mount, rel_point, speed=speed)
1209
+
1210
+ await self.retract(mount, spec.retract_target)
1211
+
1212
+ async def pick_up_tip(
1213
+ self,
1214
+ mount: top_types.Mount,
1215
+ tip_length: float,
1216
+ presses: Optional[int] = None,
1217
+ increment: Optional[float] = None,
1218
+ prep_after: bool = True,
1219
+ ) -> None:
1220
+ """
1221
+ Pick up tip from current location.
1222
+ """
1223
+
1224
+ spec, _add_tip_to_instrs = self.plan_check_pick_up_tip(
1225
+ mount=mount, presses=presses, increment=increment, tip_length=tip_length
1226
+ )
1227
+ self._backend.set_active_current(spec.plunger_currents)
1228
+ target_absolute = target_position_from_plunger(
1229
+ mount, spec.plunger_prep_pos, self._current_position
1230
+ )
1231
+ await self._move(
1232
+ target_absolute,
1233
+ home_flagged_axes=False,
1234
+ )
1235
+
1236
+ for press in spec.presses:
1237
+ with self._backend.save_current():
1238
+ self._backend.set_active_current(press.current)
1239
+ target_down = target_position_from_relative(
1240
+ mount, press.relative_down, self._current_position
1241
+ )
1242
+ await self._move(target_down, speed=press.speed)
1243
+ target_up = target_position_from_relative(
1244
+ mount, press.relative_up, self._current_position
1245
+ )
1246
+ await self._move(target_up)
1247
+ # neighboring tips tend to get stuck in the space between
1248
+ # the volume chamber and the drop-tip sleeve on p1000.
1249
+ # This extra shake ensures those tips are removed
1250
+ for rel_point, speed in spec.shake_off_list:
1251
+ await self.move_rel(mount, rel_point, speed=speed)
1252
+
1253
+ await self.retract(mount, spec.retract_target)
1254
+ _add_tip_to_instrs()
1255
+
1256
+ if prep_after:
1257
+ await self.prepare_for_aspirate(mount)
1258
+
1259
+ async def tip_drop_moves(
1260
+ self,
1261
+ mount: top_types.Mount,
1262
+ home_after: bool = True,
1263
+ ignore_plunger: bool = False,
1264
+ scrape_type: TipScrapeType = TipScrapeType.NONE,
1265
+ ) -> None:
1266
+ spec, _ = self.plan_check_drop_tip(mount, home_after)
1267
+
1268
+ for move in spec.drop_moves:
1269
+ self._backend.set_active_current(move.current)
1270
+ target_pos = target_position_from_plunger(
1271
+ mount, move.target_position, self._current_position
1272
+ )
1273
+ await self._move(
1274
+ target_pos,
1275
+ speed=move.speed,
1276
+ home_flagged_axes=False,
1277
+ )
1278
+ if move.home_after:
1279
+ smoothie_pos = await self._fast_home(
1280
+ axes=[ot2_axis_to_string(ax) for ax in move.home_axes],
1281
+ margin=move.home_after_safety_margin,
1282
+ )
1283
+ self._current_position = self.get_deck_from_machine(
1284
+ self._axis_map_from_string_map(smoothie_pos)
1285
+ )
1286
+
1287
+ for shake in spec.shake_moves:
1288
+ await self.move_rel(mount, shake[0], speed=shake[1])
1289
+
1290
+ self._backend.set_active_current(spec.ending_current)
1291
+
1292
+ async def drop_tip(self, mount: top_types.Mount, home_after: bool = True) -> None:
1293
+ """Drop tip at the current location."""
1294
+ await self.tip_drop_moves(mount, home_after)
1295
+
1296
+ # todo(mm, 2024-10-17): Ideally, callers would be able to replicate the behavior
1297
+ # of this method via self.drop_tip_moves() plus other public methods. This
1298
+ # currently prevents that: there is no public equivalent for
1299
+ # instrument.set_current_volume().
1300
+ instrument = self.get_pipette(mount)
1301
+ instrument.set_current_volume(0)
1302
+
1303
+ self.set_current_tiprack_diameter(mount, 0.0)
1304
+ self.remove_tip(mount)
1305
+
1306
+ async def create_simulating_module(
1307
+ self,
1308
+ model: modules.types.ModuleModel,
1309
+ ) -> modules.AbstractModule:
1310
+ """Get a simulating module hardware API interface for the given model."""
1311
+ assert (
1312
+ self.is_simulator
1313
+ ), "Cannot build simulating module from non-simulating hardware control API"
1314
+
1315
+ return await self._backend.module_controls.build_module(
1316
+ port="",
1317
+ usb_port=USBPort(name="", port_number=1, port_group=PortGroup.MAIN),
1318
+ type=modules.ModuleType.from_model(model),
1319
+ sim_model=model.value,
1320
+ )
1321
+
1322
+ def get_instrument_max_height(
1323
+ self, mount: top_types.Mount, critical_point: Optional[CriticalPoint] = None
1324
+ ) -> float:
1325
+ return PipetteHandlerProvider.instrument_max_height(
1326
+ self, mount, self._config.z_retract_distance, critical_point
1327
+ )
1328
+
1329
+ async def clean_up(self) -> None:
1330
+ """Get the API ready to stop cleanly."""
1331
+ await self._backend.clean_up()
1332
+
1333
+ MapPayload = TypeVar("MapPayload")
1334
+
1335
+ @staticmethod
1336
+ def _axis_map_from_string_map(
1337
+ input_map: Dict[str, "API.MapPayload"]
1338
+ ) -> Dict[Axis, "API.MapPayload"]:
1339
+ return {Axis[k]: v for k, v in input_map.items()}
1340
+
1341
+ def _string_map_from_axis_map(
1342
+ self, input_map: Dict[Axis, "API.MapPayload"]
1343
+ ) -> Dict[str, "API.MapPayload"]:
1344
+ return {ot2_axis_to_string(k): v for k, v in input_map.items()}
1345
+
1346
+ def get_estop_state(self) -> EstopState:
1347
+ return EstopState.DISENGAGED