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,1158 @@
1
+ """Protocol engine commands sub-state."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import enum
6
+ from dataclasses import dataclass
7
+ from datetime import datetime
8
+ from typing import Dict, List, Optional, Union, Tuple
9
+ from typing_extensions import assert_never
10
+
11
+ from opentrons_shared_data.errors import EnumeratedError, ErrorCodes, PythonException
12
+
13
+ from opentrons.ordered_set import OrderedSet
14
+
15
+ from opentrons.hardware_control.types import DoorState
16
+ from opentrons.protocol_engine.actions.actions import (
17
+ ResumeFromRecoveryAction,
18
+ RunCommandAction,
19
+ SetErrorRecoveryPolicyAction,
20
+ )
21
+ from opentrons.protocol_engine.commands.unsafe.unsafe_ungrip_labware import (
22
+ UnsafeUngripLabwareCommandType,
23
+ )
24
+ from opentrons.protocol_engine.commands.unsafe.unsafe_stacker_close_latch import (
25
+ UnsafeFlexStackerCloseLatchCommandType,
26
+ )
27
+ from opentrons.protocol_engine.commands.unsafe.unsafe_stacker_open_latch import (
28
+ UnsafeFlexStackerOpenLatchCommandType,
29
+ )
30
+ from opentrons.protocol_engine.error_recovery_policy import (
31
+ ErrorRecoveryPolicy,
32
+ ErrorRecoveryType,
33
+ )
34
+ from opentrons.protocol_engine.notes.notes import CommandNote
35
+ from opentrons.protocol_engine.state import update_types
36
+
37
+ from ..actions import (
38
+ Action,
39
+ QueueCommandAction,
40
+ SucceedCommandAction,
41
+ FailCommandAction,
42
+ PlayAction,
43
+ PauseAction,
44
+ StopAction,
45
+ FinishAction,
46
+ HardwareStoppedAction,
47
+ DoorChangeAction,
48
+ )
49
+
50
+ from ..commands import Command, CommandStatus, CommandIntent, CommandCreate
51
+ from ..errors import (
52
+ RunStoppedError,
53
+ ErrorOccurrence,
54
+ RobotDoorOpenError,
55
+ SetupCommandNotAllowedError,
56
+ FixitCommandNotAllowedError,
57
+ ResumeFromRecoveryNotAllowedError,
58
+ PauseNotAllowedError,
59
+ UnexpectedProtocolError,
60
+ ProtocolCommandFailedError,
61
+ )
62
+ from ..types import EngineStatus
63
+ from ._abstract_store import HasState, HandlesActions
64
+ from .command_history import (
65
+ CommandEntry,
66
+ CommandHistory,
67
+ )
68
+ from .config import Config
69
+
70
+
71
+ class QueueStatus(enum.Enum):
72
+ """Execution status of the command queue."""
73
+
74
+ SETUP = enum.auto()
75
+ """The engine has been created, but the run has not yet started.
76
+
77
+ New protocol commands may be enqueued, but will wait to execute.
78
+ New setup commands may be enqueued and will execute immediately.
79
+ New fixup commands may not be enqueued.
80
+ """
81
+
82
+ RUNNING = enum.auto()
83
+ """The queue is running through protocol commands.
84
+
85
+ New protocol commands may be enqueued and will execute immediately.
86
+ New setup commands may not be enqueued.
87
+ New fixup commands may not be enqueued.
88
+ """
89
+
90
+ PAUSED = enum.auto()
91
+ """Execution of protocol commands has been paused.
92
+
93
+ New protocol commands may be enqueued, but will wait to execute.
94
+ New setup commands may not be enqueued.
95
+ New fixup commands may not be enqueued.
96
+ """
97
+
98
+ AWAITING_RECOVERY = enum.auto()
99
+ """A protocol command has encountered a recoverable error.
100
+
101
+ New protocol commands may be enqueued, but will wait to execute.
102
+ New setup commands may not be enqueued.
103
+ New fixup commands may be enqueued and will execute immediately.
104
+ """
105
+
106
+ AWAITING_RECOVERY_PAUSED = enum.auto()
107
+ """Execution of fixit commands has been paused.
108
+
109
+ New protocol and fixit commands may be enqueued, but will usually wait to execute.
110
+ There are certain exceptions where fixit commands will still run.
111
+
112
+ New setup commands may not be enqueued.
113
+ """
114
+
115
+
116
+ class RunResult(enum.Enum):
117
+ """Result of the run."""
118
+
119
+ SUCCEEDED = enum.auto()
120
+ FAILED = enum.auto()
121
+ STOPPED = enum.auto()
122
+
123
+
124
+ @dataclass(frozen=True)
125
+ class CommandSlice:
126
+ """A subset of all commands in state."""
127
+
128
+ commands: List[Command]
129
+ cursor: int
130
+ total_length: int
131
+
132
+
133
+ @dataclass(frozen=True)
134
+ class CommandErrorSlice:
135
+ """A subset of all commands errors in state."""
136
+
137
+ commands_errors: List[ErrorOccurrence]
138
+ cursor: int
139
+ total_length: int
140
+
141
+
142
+ @dataclass(frozen=True)
143
+ class CommandPointer:
144
+ """Brief info about a command and where to find it."""
145
+
146
+ command_id: str
147
+ command_key: str
148
+ created_at: datetime
149
+ index: int
150
+
151
+
152
+ @dataclass(frozen=True)
153
+ class _RecoveryTargetInfo:
154
+ """Info about the failed command that we're currently recovering from."""
155
+
156
+ command_id: str
157
+
158
+ state_update_if_false_positive: update_types.StateUpdate
159
+ """See `CommandView.get_state_update_if_continued()`."""
160
+
161
+
162
+ @dataclass
163
+ class CommandState:
164
+ """State of all protocol engine command resources."""
165
+
166
+ command_history: CommandHistory
167
+
168
+ queue_status: QueueStatus
169
+ """Whether the engine is currently pulling new commands off the queue to execute.
170
+
171
+ A command may still be executing, and the robot may still be in motion,
172
+ even if PAUSED.
173
+ """
174
+
175
+ run_started_at: Optional[datetime]
176
+ """The time the run was started.
177
+
178
+ Set when the first `PlayAction` is dispatched.
179
+ """
180
+
181
+ run_completed_at: Optional[datetime]
182
+ """The time the run has completed.
183
+
184
+ Set when 'HardwareStoppedAction' is dispatched.
185
+ """
186
+
187
+ is_door_blocking: bool
188
+ """Whether the door is open when enable_door_safety_switch feature flag is ON."""
189
+
190
+ run_result: Optional[RunResult]
191
+ """Whether the run is done and succeeded, failed, or stopped.
192
+
193
+ This doesn't include the post-run finish steps (homing and dropping tips).
194
+
195
+ Once set, this status is immutable.
196
+ """
197
+
198
+ run_error: Optional[ErrorOccurrence]
199
+ """The run's fatal error occurrence, if there was one.
200
+
201
+ Individual command errors, which may or may not be fatal,
202
+ are stored on the individual commands themselves.
203
+ """
204
+
205
+ failed_command: Optional[CommandEntry]
206
+ """The most recent command failure, if any."""
207
+ # TODO(mm, 2024-03-19): This attribute is currently only used to help robot-server
208
+ # with pagination, but "the failed command" is an increasingly nuanced idea, now
209
+ # that we're doing error recovery. See if we can implement robot-server pagination
210
+ # atop simpler concepts, like "the last command that ran" or "the next command that
211
+ # would run."
212
+ #
213
+ # TODO(mm, 2024-04-03): Can this be replaced by
214
+ # CommandHistory.get_terminal_command() now?
215
+
216
+ command_error_recovery_types: Dict[str, ErrorRecoveryType]
217
+ """For each command that failed (indexed by ID), what its recovery type was.
218
+
219
+ This only includes commands that actually failed, not the ones that we mark as
220
+ failed but that are effectively "cancelled" because a command before them failed.
221
+
222
+ This separate attribute is a stopgap until error recovery concepts are a bit more
223
+ stable. Eventually, we might want this info to be stored directly on each command.
224
+ """
225
+
226
+ recovery_target: Optional[_RecoveryTargetInfo]
227
+ """If we're currently recovering from a command failure, info about that command."""
228
+
229
+ finish_error: Optional[ErrorOccurrence]
230
+ """The error that happened during the post-run finish steps (homing & dropping tips), if any."""
231
+
232
+ latest_protocol_command_hash: Optional[str]
233
+ """The latest PROTOCOL command hash value received in a QueueCommandAction.
234
+
235
+ This value can be used to generate future hashes.
236
+ """
237
+
238
+ has_entered_error_recovery: bool
239
+ """Whether the run has entered error recovery."""
240
+
241
+ stopped_by_estop: bool
242
+ """If this is set to True, the engine was stopped by an estop event."""
243
+
244
+ error_recovery_policy: ErrorRecoveryPolicy
245
+ """See `CommandView.get_error_recovery_policy()`."""
246
+
247
+
248
+ class CommandStore(HasState[CommandState], HandlesActions):
249
+ """Command state container for run-level command concerns."""
250
+
251
+ _state: CommandState
252
+
253
+ def __init__(
254
+ self,
255
+ *,
256
+ config: Config,
257
+ is_door_open: bool,
258
+ error_recovery_policy: ErrorRecoveryPolicy,
259
+ ) -> None:
260
+ """Initialize a CommandStore and its state."""
261
+ self._config = config
262
+ self._state = CommandState(
263
+ command_history=CommandHistory(),
264
+ queue_status=QueueStatus.SETUP,
265
+ is_door_blocking=is_door_open and config.block_on_door_open,
266
+ run_result=None,
267
+ run_error=None,
268
+ finish_error=None,
269
+ failed_command=None,
270
+ command_error_recovery_types={},
271
+ recovery_target=None,
272
+ run_completed_at=None,
273
+ run_started_at=None,
274
+ latest_protocol_command_hash=None,
275
+ stopped_by_estop=False,
276
+ error_recovery_policy=error_recovery_policy,
277
+ has_entered_error_recovery=False,
278
+ )
279
+
280
+ def handle_action(self, action: Action) -> None:
281
+ """Modify state in reaction to an action."""
282
+ match action:
283
+ case QueueCommandAction():
284
+ self._handle_queue_command_action(action)
285
+ case RunCommandAction():
286
+ self._handle_run_command_action(action)
287
+ case SucceedCommandAction():
288
+ self._handle_succeed_command_action(action)
289
+ case FailCommandAction():
290
+ self._handle_fail_command_action(action)
291
+ case PlayAction():
292
+ self._handle_play_action(action)
293
+ case PauseAction():
294
+ self._handle_pause_action(action)
295
+ case ResumeFromRecoveryAction():
296
+ self._handle_resume_from_recovery_action(action)
297
+ case StopAction():
298
+ self._handle_stop_action(action)
299
+ case FinishAction():
300
+ self._handle_finish_action(action)
301
+ case HardwareStoppedAction():
302
+ self._handle_hardware_stopped_action(action)
303
+ case DoorChangeAction():
304
+ self._handle_door_change_action(action)
305
+ case SetErrorRecoveryPolicyAction():
306
+ self._handle_set_error_recovery_policy_action(action)
307
+ case _:
308
+ pass
309
+
310
+ def clear_history(self) -> None:
311
+ """Clears CommandHistory state."""
312
+ self._state.command_history.clear()
313
+
314
+ def _handle_queue_command_action(self, action: QueueCommandAction) -> None:
315
+ # TODO(mc, 2021-06-22): mypy has trouble with this automatic
316
+ # request > command mapping, figure out how to type precisely
317
+ # (or wait for a future mypy version that can figure it out).
318
+ queued_command = action.request._CommandCls.model_construct(
319
+ id=action.command_id,
320
+ key=(
321
+ action.request.key
322
+ if action.request.key is not None
323
+ else (action.request_hash or action.command_id)
324
+ ),
325
+ createdAt=action.created_at,
326
+ params=action.request.params, # type: ignore[arg-type]
327
+ intent=action.request.intent,
328
+ status=CommandStatus.QUEUED,
329
+ failedCommandId=action.failed_command_id,
330
+ )
331
+
332
+ self._state.command_history.append_queued_command(queued_command)
333
+
334
+ if action.request_hash is not None:
335
+ self._state.latest_protocol_command_hash = action.request_hash
336
+
337
+ def _handle_run_command_action(self, action: RunCommandAction) -> None:
338
+ prev_entry = self._state.command_history.get(action.command_id)
339
+
340
+ running_command = prev_entry.command.model_copy(
341
+ update={
342
+ "status": CommandStatus.RUNNING,
343
+ "startedAt": action.started_at,
344
+ }
345
+ )
346
+
347
+ self._state.command_history.set_command_running(running_command)
348
+
349
+ def _handle_succeed_command_action(self, action: SucceedCommandAction) -> None:
350
+ succeeded_command = action.command
351
+ self._state.command_history.set_command_succeeded(succeeded_command)
352
+
353
+ def _handle_fail_command_action( # noqa: C901
354
+ self, action: FailCommandAction
355
+ ) -> None:
356
+ prev_entry = self.state.command_history.get(action.command_id)
357
+
358
+ if isinstance(action.error, EnumeratedError): # The error was undefined.
359
+ public_error_occurrence = ErrorOccurrence.from_failed(
360
+ id=action.error_id,
361
+ createdAt=action.failed_at,
362
+ error=action.error,
363
+ )
364
+ # An empty state update, to no-op.
365
+ state_update_if_false_positive = update_types.StateUpdate()
366
+ else: # The error was defined.
367
+ public_error_occurrence = action.error.public
368
+ state_update_if_false_positive = action.error.state_update_if_false_positive
369
+
370
+ self._update_to_failed(
371
+ command_id=action.command_id,
372
+ failed_at=action.failed_at,
373
+ error_occurrence=public_error_occurrence,
374
+ error_recovery_type=action.type,
375
+ notes=action.notes,
376
+ )
377
+ self._state.failed_command = self._state.command_history.get(action.command_id)
378
+
379
+ if (
380
+ prev_entry.command.intent in (CommandIntent.PROTOCOL, None)
381
+ and action.type == ErrorRecoveryType.WAIT_FOR_RECOVERY
382
+ ):
383
+ self._state.queue_status = QueueStatus.AWAITING_RECOVERY
384
+ self._state.recovery_target = _RecoveryTargetInfo(
385
+ command_id=action.command_id,
386
+ state_update_if_false_positive=state_update_if_false_positive,
387
+ )
388
+ self._state.has_entered_error_recovery = True
389
+
390
+ # When one command fails, we generally also cancel the commands that
391
+ # would have been queued after it.
392
+ other_command_ids_to_fail: List[str]
393
+ if prev_entry.command.intent == CommandIntent.SETUP:
394
+ other_command_ids_to_fail = list(
395
+ self._state.command_history.get_setup_queue_ids()
396
+ )
397
+ elif prev_entry.command.intent == CommandIntent.FIXIT:
398
+ if (
399
+ action.type == ErrorRecoveryType.CONTINUE_WITH_ERROR
400
+ or action.type == ErrorRecoveryType.ASSUME_FALSE_POSITIVE_AND_CONTINUE
401
+ ):
402
+ other_command_ids_to_fail = []
403
+ else:
404
+ other_command_ids_to_fail = list(
405
+ self._state.command_history.get_fixit_queue_ids()
406
+ )
407
+ elif (
408
+ prev_entry.command.intent == CommandIntent.PROTOCOL
409
+ or prev_entry.command.intent is None
410
+ ):
411
+ if action.type == ErrorRecoveryType.FAIL_RUN:
412
+ other_command_ids_to_fail = list(
413
+ self._state.command_history.get_queue_ids()
414
+ )
415
+ elif (
416
+ action.type == ErrorRecoveryType.WAIT_FOR_RECOVERY
417
+ or action.type == ErrorRecoveryType.CONTINUE_WITH_ERROR
418
+ or action.type == ErrorRecoveryType.ASSUME_FALSE_POSITIVE_AND_CONTINUE
419
+ ):
420
+ other_command_ids_to_fail = []
421
+ else:
422
+ assert_never(action.type)
423
+ else:
424
+ assert_never(prev_entry.command.intent)
425
+ for command_id in other_command_ids_to_fail:
426
+ # TODO(mc, 2022-06-06): add new "cancelled" status or similar
427
+ self._update_to_failed(
428
+ command_id=command_id,
429
+ failed_at=action.failed_at,
430
+ error_occurrence=None,
431
+ error_recovery_type=None,
432
+ notes=None,
433
+ )
434
+
435
+ def _handle_play_action(self, action: PlayAction) -> None:
436
+ if not self._state.run_result:
437
+ self._state.run_started_at = (
438
+ self._state.run_started_at or action.requested_at
439
+ )
440
+ match self._state.queue_status:
441
+ case QueueStatus.SETUP:
442
+ self._state.queue_status = (
443
+ QueueStatus.PAUSED
444
+ if self._state.is_door_blocking
445
+ else QueueStatus.RUNNING
446
+ )
447
+ case QueueStatus.AWAITING_RECOVERY_PAUSED:
448
+ self._state.queue_status = QueueStatus.AWAITING_RECOVERY
449
+ case QueueStatus.PAUSED:
450
+ self._state.queue_status = QueueStatus.RUNNING
451
+ case QueueStatus.RUNNING | QueueStatus.AWAITING_RECOVERY:
452
+ # Nothing for the play action to do. No-op.
453
+ pass
454
+
455
+ def _handle_pause_action(self, action: PauseAction) -> None:
456
+ self._state.queue_status = QueueStatus.PAUSED
457
+
458
+ def _handle_resume_from_recovery_action(
459
+ self, action: ResumeFromRecoveryAction
460
+ ) -> None:
461
+ self._state.queue_status = QueueStatus.RUNNING
462
+ self._state.recovery_target = None
463
+
464
+ def _handle_stop_action(self, action: StopAction) -> None:
465
+ if not self._state.run_result:
466
+ self._state.recovery_target = None
467
+ self._state.queue_status = QueueStatus.PAUSED
468
+
469
+ if action.from_estop:
470
+ self._state.stopped_by_estop = True
471
+ self._state.run_result = RunResult.FAILED
472
+ else:
473
+ self._state.run_result = RunResult.STOPPED
474
+
475
+ def _handle_finish_action(self, action: FinishAction) -> None:
476
+ if not self._state.run_result:
477
+ self._state.recovery_target = None
478
+ self._state.queue_status = QueueStatus.PAUSED
479
+
480
+ if action.set_run_status:
481
+ self._state.run_result = (
482
+ RunResult.SUCCEEDED
483
+ if not action.error_details
484
+ else RunResult.FAILED
485
+ )
486
+ else:
487
+ self._state.run_result = RunResult.STOPPED
488
+
489
+ if not self._state.run_error and action.error_details:
490
+ self._state.run_error = self._map_run_exception_to_error_occurrence(
491
+ action.error_details.error_id,
492
+ action.error_details.created_at,
493
+ action.error_details.error,
494
+ )
495
+ else:
496
+ # HACK(sf): There needs to be a better way to set
497
+ # an estop error than this else clause
498
+ if self._state.stopped_by_estop and action.error_details:
499
+ self._state.run_error = self._map_run_exception_to_error_occurrence(
500
+ action.error_details.error_id,
501
+ action.error_details.created_at,
502
+ action.error_details.error,
503
+ )
504
+
505
+ def _handle_hardware_stopped_action(self, action: HardwareStoppedAction) -> None:
506
+ self._state.queue_status = QueueStatus.PAUSED
507
+ self._state.run_result = self._state.run_result or RunResult.STOPPED
508
+ self._state.run_completed_at = (
509
+ self._state.run_completed_at or action.completed_at
510
+ )
511
+
512
+ if action.finish_error_details:
513
+ self._state.finish_error = self._map_finish_exception_to_error_occurrence(
514
+ action.finish_error_details.error_id,
515
+ action.finish_error_details.created_at,
516
+ action.finish_error_details.error,
517
+ )
518
+
519
+ def _handle_door_change_action(self, action: DoorChangeAction) -> None:
520
+ if self._config.block_on_door_open:
521
+ if action.door_state == DoorState.OPEN:
522
+ self._state.is_door_blocking = True
523
+ match self._state.queue_status:
524
+ case QueueStatus.SETUP:
525
+ pass
526
+ case QueueStatus.RUNNING | QueueStatus.PAUSED:
527
+ self._state.queue_status = QueueStatus.PAUSED
528
+ case (
529
+ QueueStatus.AWAITING_RECOVERY
530
+ | QueueStatus.AWAITING_RECOVERY_PAUSED
531
+ ):
532
+ self._state.queue_status = QueueStatus.AWAITING_RECOVERY_PAUSED
533
+ elif action.door_state == DoorState.CLOSED:
534
+ self._state.is_door_blocking = False
535
+
536
+ def _handle_set_error_recovery_policy_action(
537
+ self, action: SetErrorRecoveryPolicyAction
538
+ ) -> None:
539
+ self._state.error_recovery_policy = action.error_recovery_policy
540
+
541
+ def _update_to_failed(
542
+ self,
543
+ command_id: str,
544
+ failed_at: datetime,
545
+ error_occurrence: Optional[ErrorOccurrence],
546
+ error_recovery_type: Optional[ErrorRecoveryType],
547
+ notes: Optional[List[CommandNote]],
548
+ ) -> None:
549
+ prev_entry = self._state.command_history.get(command_id)
550
+ failed_command = prev_entry.command.model_copy(
551
+ update={
552
+ "completedAt": failed_at,
553
+ "status": CommandStatus.FAILED,
554
+ **({"error": error_occurrence} if error_occurrence is not None else {}),
555
+ # Assume we're not overwriting any existing notes because they can
556
+ # only be added when a command completes, and if we're failing this
557
+ # command, it wouldn't have completed before now.
558
+ **({"notes": notes} if notes is not None else {}),
559
+ }
560
+ )
561
+ self._state.command_history.set_command_failed(failed_command)
562
+ if error_recovery_type is not None:
563
+ self._state.command_error_recovery_types[command_id] = error_recovery_type
564
+
565
+ @staticmethod
566
+ def _map_run_exception_to_error_occurrence(
567
+ error_id: str, created_at: datetime, exception: Exception
568
+ ) -> ErrorOccurrence:
569
+ """Map a fatal exception from the main part of the run to an ErrorOccurrence."""
570
+ if (
571
+ isinstance(exception, ProtocolCommandFailedError)
572
+ and exception.original_error is not None
573
+ ):
574
+ return exception.original_error
575
+ elif isinstance(exception, EnumeratedError):
576
+ return ErrorOccurrence.from_failed(
577
+ id=error_id, createdAt=created_at, error=exception
578
+ )
579
+ else:
580
+ enumerated_wrapper = UnexpectedProtocolError(
581
+ message=str(exception),
582
+ wrapping=[exception],
583
+ )
584
+ return ErrorOccurrence.from_failed(
585
+ id=error_id, createdAt=created_at, error=enumerated_wrapper
586
+ )
587
+
588
+ @staticmethod
589
+ def _map_finish_exception_to_error_occurrence(
590
+ error_id: str, created_at: datetime, exception: Exception
591
+ ) -> ErrorOccurrence:
592
+ """Map a fatal exception from the finish phase (drop tip & home) to an ErrorOccurrence."""
593
+ if isinstance(exception, EnumeratedError):
594
+ return ErrorOccurrence.from_failed(
595
+ id=error_id, createdAt=created_at, error=exception
596
+ )
597
+ else:
598
+ enumerated_wrapper = PythonException(exc=exception)
599
+ return ErrorOccurrence.from_failed(
600
+ id=error_id, createdAt=created_at, error=enumerated_wrapper
601
+ )
602
+
603
+
604
+ class CommandView:
605
+ """Read-only command state view."""
606
+
607
+ _state: CommandState
608
+
609
+ def __init__(self, state: CommandState) -> None:
610
+ """Initialize the view of command state with its underlying data."""
611
+ self._state = state
612
+
613
+ def get(self, command_id: str) -> Command:
614
+ """Get a command by its unique identifier."""
615
+ return self._state.command_history.get(command_id).command
616
+
617
+ def get_all(self) -> List[Command]:
618
+ """Get a list of all commands in state.
619
+
620
+ Entries are returned in the order of first-added command to last-added command.
621
+ Replacing a command (to change its status, for example) keeps its place in the
622
+ ordering.
623
+ """
624
+ return self._state.command_history.get_all_commands()
625
+
626
+ def get_slice(
627
+ self, cursor: Optional[int], length: int, include_fixit_commands: bool
628
+ ) -> CommandSlice:
629
+ """Get a subset of commands around a given cursor.
630
+
631
+ If the cursor is omitted, a cursor will be selected automatically
632
+ based on the currently running or most recently executed command,
633
+ and the slice of commands returned is the previous `length` commands
634
+ inclusive of the currently running or most recently executed command.
635
+ """
636
+ command_ids = self._state.command_history.get_filtered_command_ids(
637
+ include_fixit_commands=include_fixit_commands
638
+ )
639
+ total_length = len(command_ids)
640
+
641
+ if cursor is None:
642
+ current_pointer = self.get_current()
643
+
644
+ if current_pointer is not None:
645
+ cursor = current_pointer.index
646
+ else:
647
+ cursor = total_length - 1
648
+
649
+ cursor = max(cursor - length + 1, 0)
650
+
651
+ # start is inclusive, stop is exclusive
652
+ start = max(0, min(cursor, total_length - 1))
653
+ stop = min(total_length, start + length)
654
+ commands = self._state.command_history.get_slice(
655
+ start=start, stop=stop, command_ids=command_ids
656
+ )
657
+
658
+ return CommandSlice(
659
+ commands=commands,
660
+ cursor=start,
661
+ total_length=total_length,
662
+ )
663
+
664
+ def get_errors_slice(
665
+ self,
666
+ cursor: int,
667
+ length: int,
668
+ ) -> CommandErrorSlice:
669
+ """Get a subset of commands error around a given cursor."""
670
+ # start is inclusive, stop is exclusive
671
+ all_errors = self.get_all_errors()
672
+ total_length = len(all_errors)
673
+ actual_cursor = max(0, min(cursor, total_length - 1))
674
+ stop = min(total_length, actual_cursor + length)
675
+
676
+ sliced_errors = all_errors[actual_cursor:stop]
677
+
678
+ return CommandErrorSlice(
679
+ commands_errors=sliced_errors,
680
+ cursor=actual_cursor,
681
+ total_length=total_length,
682
+ )
683
+
684
+ def get_error(self) -> Optional[ErrorOccurrence]:
685
+ """Get the run's fatal error, if there was one."""
686
+ run_error = self._state.run_error
687
+ finish_error = self._state.finish_error
688
+
689
+ if run_error and finish_error:
690
+ combined_error = ErrorOccurrence(
691
+ id=finish_error.id,
692
+ createdAt=finish_error.createdAt,
693
+ errorType="RunAndFinishFailed",
694
+ detail=(
695
+ "The run had a fatal error,"
696
+ " and another error happened while doing post-run cleanup."
697
+ ),
698
+ # TODO(mm, 2023-07-31): Consider adding a low-priority error code so clients can
699
+ # deemphasize this root node, in favor of its children in wrappedErrors.
700
+ errorCode=ErrorCodes.GENERAL_ERROR.value.code,
701
+ wrappedErrors=[
702
+ run_error,
703
+ finish_error,
704
+ ],
705
+ )
706
+ return combined_error
707
+ else:
708
+ return run_error or finish_error
709
+
710
+ def get_all_errors(self) -> List[ErrorOccurrence]:
711
+ """Get the run's full error list, if there was none, returns an empty list."""
712
+ failed_commands = self._state.command_history.get_all_failed_commands()
713
+ return [
714
+ command_error.error
715
+ for command_error in failed_commands
716
+ if command_error.error is not None
717
+ ]
718
+
719
+ def get_has_entered_recovery_mode(self) -> bool:
720
+ """Get whether the run has entered recovery mode."""
721
+ return self._state.has_entered_error_recovery
722
+
723
+ def get_running_command_id(self) -> Optional[str]:
724
+ """Return the ID of the command that's currently running, if there is one."""
725
+ running_command = self._state.command_history.get_running_command()
726
+ if running_command is not None:
727
+ return running_command.command.id
728
+ else:
729
+ return None
730
+
731
+ def get_queue_ids(self) -> OrderedSet[str]:
732
+ """Get the IDs of all queued protocol commands, in FIFO order."""
733
+ return self._state.command_history.get_queue_ids()
734
+
735
+ def get_current(self) -> Optional[CommandPointer]:
736
+ """Return the "current" command, if any.
737
+
738
+ The "current" command is the command that is currently executing,
739
+ or the most recent command to have completed.
740
+ """
741
+ running_command = self._state.command_history.get_running_command()
742
+ if running_command:
743
+ return CommandPointer(
744
+ command_id=running_command.command.id,
745
+ command_key=running_command.command.key,
746
+ created_at=running_command.command.createdAt,
747
+ index=running_command.index,
748
+ )
749
+
750
+ most_recently_finalized_command = self.get_most_recently_finalized_command()
751
+ if most_recently_finalized_command:
752
+ return CommandPointer(
753
+ command_id=most_recently_finalized_command.command.id,
754
+ command_key=most_recently_finalized_command.command.key,
755
+ created_at=most_recently_finalized_command.command.createdAt,
756
+ index=most_recently_finalized_command.index,
757
+ )
758
+
759
+ return None
760
+
761
+ def get_next_to_execute(self) -> Optional[str]:
762
+ """Return the next command in line to be executed.
763
+
764
+ Returns:
765
+ The ID of the earliest queued command, if any.
766
+
767
+ Raises:
768
+ RunStoppedError: The engine is currently stopped or stopping,
769
+ so it will never run any more commands.
770
+ """
771
+ if self._state.run_result:
772
+ raise RunStoppedError("Engine was stopped")
773
+
774
+ # if queue is in recovery mode, return the next fixit command
775
+ next_fixit_cmd = self._state.command_history.get_fixit_queue_ids().head(None)
776
+ if next_fixit_cmd and self._state.queue_status == QueueStatus.AWAITING_RECOVERY:
777
+ return next_fixit_cmd
778
+ if (
779
+ next_fixit_cmd
780
+ and self._state.queue_status == QueueStatus.AWAITING_RECOVERY_PAUSED
781
+ and self._may_run_with_door_open(fixit_command=self.get(next_fixit_cmd))
782
+ ):
783
+ return next_fixit_cmd
784
+
785
+ # if there is a setup command queued, prioritize it
786
+ next_setup_cmd = self._state.command_history.get_setup_queue_ids().head(None)
787
+ if (
788
+ self._state.queue_status
789
+ not in [QueueStatus.PAUSED, QueueStatus.AWAITING_RECOVERY]
790
+ and next_setup_cmd
791
+ ):
792
+ return next_setup_cmd
793
+
794
+ # if the queue is running, return the next protocol command
795
+ if self._state.queue_status == QueueStatus.RUNNING:
796
+ return self._state.command_history.get_queue_ids().head(None)
797
+
798
+ # otherwise we've got nothing to do
799
+ return None
800
+
801
+ def get_is_okay_to_clear(self) -> bool:
802
+ """Get whether the engine is stopped or sitting idly so it could be removed."""
803
+ if self.get_is_stopped():
804
+ return True
805
+ elif (
806
+ self.get_status() == EngineStatus.IDLE
807
+ and self._state.command_history.get_running_command() is None
808
+ and len(self._state.command_history.get_setup_queue_ids()) == 0
809
+ ):
810
+ return True
811
+ else:
812
+ return False
813
+
814
+ def get_is_door_blocking(self) -> bool:
815
+ """Get whether the robot door is open when 'pause on door open' ff is True."""
816
+ return self._state.is_door_blocking
817
+
818
+ def get_is_running(self) -> bool:
819
+ """Get whether the protocol is running & queued commands should be executed."""
820
+ return self._state.queue_status == QueueStatus.RUNNING
821
+
822
+ def get_most_recently_finalized_command(self) -> Optional[CommandEntry]:
823
+ """Get the most recent command that has reached its final `status`. See get_command_is_final."""
824
+ run_requested_to_stop = self._state.run_result is not None
825
+
826
+ if run_requested_to_stop:
827
+ tail_command = self._state.command_history.get_tail_command()
828
+ if not tail_command:
829
+ return None
830
+ if tail_command.command.status != CommandStatus.RUNNING:
831
+ return tail_command
832
+ else:
833
+ return self._state.command_history.get_prev(tail_command.command.id)
834
+ else:
835
+ most_recently_finalized = (
836
+ self._state.command_history.get_most_recently_completed_command()
837
+ )
838
+ # This iteration is effectively O(1) as we'll only ever have to iterate one or two times at most.
839
+ while most_recently_finalized is not None:
840
+ next_command = self._state.command_history.get_next(
841
+ most_recently_finalized.command.id
842
+ )
843
+ if (
844
+ next_command is not None
845
+ and next_command.command.status != CommandStatus.QUEUED
846
+ and next_command.command.status != CommandStatus.RUNNING
847
+ ):
848
+ most_recently_finalized = next_command
849
+ else:
850
+ break
851
+
852
+ return most_recently_finalized
853
+
854
+ def get_command_is_final(self, command_id: str) -> bool:
855
+ """Get whether a given command has reached its final `status`.
856
+
857
+ This happens when one of the following is true:
858
+
859
+ - Its status is `CommandStatus.SUCCEEDED`.
860
+ - Its status is `CommandStatus.FAILED`.
861
+ - Its status is `CommandStatus.QUEUED` but the run has been requested to stop,
862
+ so the run will never reach it.
863
+
864
+ Arguments:
865
+ command_id: Command to check.
866
+ """
867
+ status = self.get(command_id).status
868
+
869
+ run_requested_to_stop = self._state.run_result is not None
870
+
871
+ return (
872
+ status == CommandStatus.SUCCEEDED
873
+ or status == CommandStatus.FAILED
874
+ or (status == CommandStatus.QUEUED and run_requested_to_stop)
875
+ )
876
+
877
+ def get_all_commands_final(self) -> bool:
878
+ """Get whether all commands added so far have reached their final `status`.
879
+
880
+ See `get_command_is_final()`.
881
+
882
+ Raises:
883
+ CommandExecutionFailedError: if any added command failed, and its `intent` wasn't
884
+ `setup`.
885
+ """
886
+ no_command_running = self._state.command_history.get_running_command() is None
887
+ run_requested_to_stop = self._state.run_result is not None
888
+ no_command_to_execute = (
889
+ run_requested_to_stop
890
+ # TODO(mm, 2024-03-15): This ignores queued setup commands,
891
+ # which seems questionable?
892
+ or len(self._state.command_history.get_queue_ids()) == 0
893
+ )
894
+
895
+ return no_command_running and no_command_to_execute
896
+
897
+ def get_recovery_target(self) -> Optional[CommandPointer]:
898
+ """Return the command currently undergoing error recovery, if any."""
899
+ recovery_target = self._state.recovery_target
900
+ if recovery_target is None:
901
+ return None
902
+ else:
903
+ entry = self._state.command_history.get(recovery_target.command_id)
904
+ return CommandPointer(
905
+ command_id=entry.command.id,
906
+ command_key=entry.command.key,
907
+ created_at=entry.command.createdAt,
908
+ index=entry.index,
909
+ )
910
+
911
+ def get_recovery_in_progress_for_command(self, command_id: str) -> bool:
912
+ """Return whether the given command failed and its error recovery is in progress."""
913
+ pointer = self.get_recovery_target()
914
+ return pointer is not None and pointer.command_id == command_id
915
+
916
+ def raise_fatal_command_error(self) -> None:
917
+ """Raise the run's fatal command error, if there was one, as an exception.
918
+
919
+ The "fatal command error" is the error from any non-setup command.
920
+ It's intended to be used as the fatal error of the overall run
921
+ (see `ProtocolEngine.finish()`) for JSON and live HTTP protocols.
922
+
923
+ This isn't useful for Python protocols, which have to account for the
924
+ fatal error of the overall run coming from anywhere in the Python script,
925
+ including in between commands.
926
+ """
927
+ failed_command = self._state.failed_command
928
+ if (
929
+ failed_command
930
+ and failed_command.command.error
931
+ and failed_command.command.intent != CommandIntent.SETUP
932
+ ):
933
+ raise ProtocolCommandFailedError(
934
+ original_error=failed_command.command.error,
935
+ message=failed_command.command.error.detail,
936
+ )
937
+
938
+ def get_error_recovery_type(self, command_id: str) -> ErrorRecoveryType:
939
+ """Return the error recovery type with which the given command failed.
940
+
941
+ The command ID is assumed to point to a failed command.
942
+ """
943
+ return self._state.command_error_recovery_types[command_id]
944
+
945
+ def get_is_stopped(self) -> bool:
946
+ """Get whether an engine stop has completed."""
947
+ return self._state.run_completed_at is not None
948
+
949
+ def get_is_stopped_by_estop(self) -> bool:
950
+ """Return whether the engine was stopped specifically by an E-stop."""
951
+ return self._state.stopped_by_estop
952
+
953
+ def has_been_played(self) -> bool:
954
+ """Get whether engine has started."""
955
+ return self._state.run_started_at is not None
956
+
957
+ def get_is_terminal(self) -> bool:
958
+ """Get whether engine is in a terminal state."""
959
+ return self._state.run_result is not None
960
+
961
+ def validate_action_allowed( # noqa: C901
962
+ self,
963
+ action: Union[
964
+ PlayAction,
965
+ PauseAction,
966
+ StopAction,
967
+ ResumeFromRecoveryAction,
968
+ QueueCommandAction,
969
+ ],
970
+ ) -> Union[
971
+ PlayAction,
972
+ PauseAction,
973
+ StopAction,
974
+ ResumeFromRecoveryAction,
975
+ QueueCommandAction,
976
+ ]:
977
+ """Validate whether a given control action is allowed.
978
+
979
+ Returns:
980
+ The action, if valid.
981
+
982
+ Raises:
983
+ RunStoppedError: The engine has been stopped.
984
+ RobotDoorOpenError: Cannot resume because the front door is open.
985
+ PauseNotAllowedError: The engine is not running, so cannot be paused.
986
+ SetupCommandNotAllowedError: The engine is running, so a setup command
987
+ may not be added.
988
+ """
989
+ if self._state.run_result is not None:
990
+ raise RunStoppedError("The run has already stopped.")
991
+
992
+ elif isinstance(action, PlayAction):
993
+ if self.get_status() in (
994
+ EngineStatus.BLOCKED_BY_OPEN_DOOR,
995
+ EngineStatus.AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR,
996
+ ):
997
+ raise RobotDoorOpenError("Front door or top window is currently open.")
998
+ else:
999
+ return action
1000
+
1001
+ elif isinstance(action, PauseAction):
1002
+ if not self.get_is_running():
1003
+ raise PauseNotAllowedError("Cannot pause a run that is not running.")
1004
+ elif self.get_status() == EngineStatus.AWAITING_RECOVERY:
1005
+ raise PauseNotAllowedError("Cannot pause a run in recovery mode.")
1006
+ else:
1007
+ return action
1008
+
1009
+ elif isinstance(action, QueueCommandAction):
1010
+ if (
1011
+ action.request.intent == CommandIntent.SETUP
1012
+ and self._state.queue_status != QueueStatus.SETUP
1013
+ ):
1014
+ raise SetupCommandNotAllowedError(
1015
+ "Setup commands are not allowed after run has started."
1016
+ )
1017
+ elif action.request.intent == CommandIntent.FIXIT:
1018
+ if self.get_status() == EngineStatus.AWAITING_RECOVERY:
1019
+ return action
1020
+ elif self.get_status() in (
1021
+ EngineStatus.AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR,
1022
+ EngineStatus.AWAITING_RECOVERY_PAUSED,
1023
+ ):
1024
+ if self._may_run_with_door_open(fixit_command=action.request):
1025
+ return action
1026
+ else:
1027
+ raise FixitCommandNotAllowedError(
1028
+ f"{action.request.commandType} fixit command may not run"
1029
+ " until the door is closed and the run is played again."
1030
+ )
1031
+ else:
1032
+ raise FixitCommandNotAllowedError(
1033
+ "Fixit commands are not allowed when the run is not in a recoverable state."
1034
+ )
1035
+ else:
1036
+ return action
1037
+
1038
+ elif isinstance(action, ResumeFromRecoveryAction):
1039
+ if self.get_status() != EngineStatus.AWAITING_RECOVERY:
1040
+ raise ResumeFromRecoveryNotAllowedError(
1041
+ "Cannot resume from recovery if the run is not in recovery mode."
1042
+ )
1043
+ elif (
1044
+ self.get_status() == EngineStatus.AWAITING_RECOVERY
1045
+ and len(self._state.command_history.get_fixit_queue_ids()) > 0
1046
+ ):
1047
+ raise ResumeFromRecoveryNotAllowedError(
1048
+ "Cannot resume from recovery while there are fixit commands in the queue."
1049
+ )
1050
+ else:
1051
+ return action
1052
+
1053
+ elif isinstance(action, StopAction):
1054
+ return action
1055
+
1056
+ else:
1057
+ assert_never(action)
1058
+
1059
+ def get_status(self) -> EngineStatus: # noqa: C901
1060
+ """Get the current execution status of the engine."""
1061
+ if self._state.run_result:
1062
+ # The main part of the run is over, or will be over soon.
1063
+ # Have we also completed the post-run finish steps (homing and dropping tips)?
1064
+ if self.get_is_stopped():
1065
+ # Post-run finish steps have completed. Calculate the engine's final status,
1066
+ # taking into account any failures in the run or the post-run finish steps.
1067
+ if (
1068
+ self._state.run_result == RunResult.FAILED
1069
+ or self._state.finish_error is not None
1070
+ ):
1071
+ return EngineStatus.FAILED
1072
+ elif self._state.run_result == RunResult.SUCCEEDED:
1073
+ return EngineStatus.SUCCEEDED
1074
+ else:
1075
+ return EngineStatus.STOPPED
1076
+ else:
1077
+ # Post-run finish steps have not yet completed,
1078
+ # and we may even still be executing commands.
1079
+ return (
1080
+ EngineStatus.STOP_REQUESTED
1081
+ if self._state.run_result == RunResult.STOPPED
1082
+ else EngineStatus.FINISHING
1083
+ )
1084
+
1085
+ elif self._state.queue_status == QueueStatus.RUNNING:
1086
+ return EngineStatus.RUNNING
1087
+
1088
+ elif self._state.queue_status == QueueStatus.PAUSED:
1089
+ if self._state.is_door_blocking:
1090
+ return EngineStatus.BLOCKED_BY_OPEN_DOOR
1091
+ else:
1092
+ return EngineStatus.PAUSED
1093
+
1094
+ elif self._state.queue_status == QueueStatus.AWAITING_RECOVERY_PAUSED:
1095
+ if self._state.is_door_blocking:
1096
+ return EngineStatus.AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR
1097
+ else:
1098
+ return EngineStatus.AWAITING_RECOVERY_PAUSED
1099
+
1100
+ elif self._state.queue_status == QueueStatus.AWAITING_RECOVERY:
1101
+ return EngineStatus.AWAITING_RECOVERY
1102
+
1103
+ # todo(mm, 2024-03-19): Does this intentionally return idle if QueueStatus is
1104
+ # SETUP and we're currently a setup command?
1105
+ return EngineStatus.IDLE
1106
+
1107
+ def get_latest_protocol_command_hash(self) -> Optional[str]:
1108
+ """Get the command hash of the last queued command, if any."""
1109
+ return self._state.latest_protocol_command_hash
1110
+
1111
+ def get_error_recovery_policy(self) -> ErrorRecoveryPolicy:
1112
+ """Return the run's current error recovery policy (see `ErrorRecoveryPolicy`).
1113
+
1114
+ This error recovery policy is not ever evaluated by
1115
+ `CommandStore`/`CommandView`. It's stored here for convenience, but evaluated by
1116
+ higher-level code.
1117
+ """
1118
+ return self._state.error_recovery_policy
1119
+
1120
+ def get_state_update_for_false_positive(self) -> update_types.StateUpdate:
1121
+ """Return the state update for if the current recovery target was a false positive.
1122
+
1123
+ If we're currently in error recovery mode, and you have decided that the
1124
+ underlying command error was a false positive, this returns a state update
1125
+ that will undo the error's effects on engine state.
1126
+ See `ProtocolEngine.resume_from_recovery(reconcile_false_positive=True)`.
1127
+ """
1128
+ if self._state.recovery_target is None:
1129
+ return update_types.StateUpdate() # Empty/no-op.
1130
+ else:
1131
+ return self._state.recovery_target.state_update_if_false_positive
1132
+
1133
+ def _may_run_with_door_open(
1134
+ self, *, fixit_command: Command | CommandCreate
1135
+ ) -> bool:
1136
+ """Return whether the given fixit command is exempt from the usual open-door auto pause.
1137
+
1138
+ This is required for certain error recovery flows, where we want the robot to
1139
+ do stuff while the door is open.
1140
+ """
1141
+ # CommandIntent.PROTOCOL and CommandIntent.SETUP have their own rules for whether
1142
+ # they run while the door is open. Passing one of those commands to this function
1143
+ # is probably a mistake in the caller's logic.
1144
+ assert fixit_command.intent == CommandIntent.FIXIT
1145
+
1146
+ # These type annotations are to make sure the string constants stay in sync and aren't typo'd.
1147
+ allowed_command_types: Tuple[
1148
+ UnsafeUngripLabwareCommandType,
1149
+ UnsafeFlexStackerCloseLatchCommandType,
1150
+ UnsafeFlexStackerOpenLatchCommandType,
1151
+ ] = (
1152
+ "unsafe/ungripLabware",
1153
+ "unsafe/flexStacker/closeLatch",
1154
+ "unsafe/flexStacker/openLatch",
1155
+ )
1156
+ # todo(mm, 2024-10-04): Instead of allowlisting command types, maybe we should
1157
+ # add a `mayRunWithDoorOpen: bool` field to command requests.
1158
+ return fixit_command.commandType in allowed_command_types