opentrons 8.6.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (601) hide show
  1. opentrons/__init__.py +150 -0
  2. opentrons/_version.py +34 -0
  3. opentrons/calibration_storage/__init__.py +54 -0
  4. opentrons/calibration_storage/deck_configuration.py +62 -0
  5. opentrons/calibration_storage/encoder_decoder.py +31 -0
  6. opentrons/calibration_storage/file_operators.py +142 -0
  7. opentrons/calibration_storage/helpers.py +103 -0
  8. opentrons/calibration_storage/ot2/__init__.py +34 -0
  9. opentrons/calibration_storage/ot2/deck_attitude.py +85 -0
  10. opentrons/calibration_storage/ot2/mark_bad_calibration.py +27 -0
  11. opentrons/calibration_storage/ot2/models/__init__.py +0 -0
  12. opentrons/calibration_storage/ot2/models/v1.py +149 -0
  13. opentrons/calibration_storage/ot2/pipette_offset.py +129 -0
  14. opentrons/calibration_storage/ot2/tip_length.py +281 -0
  15. opentrons/calibration_storage/ot3/__init__.py +31 -0
  16. opentrons/calibration_storage/ot3/deck_attitude.py +83 -0
  17. opentrons/calibration_storage/ot3/gripper_offset.py +156 -0
  18. opentrons/calibration_storage/ot3/models/__init__.py +0 -0
  19. opentrons/calibration_storage/ot3/models/v1.py +122 -0
  20. opentrons/calibration_storage/ot3/module_offset.py +138 -0
  21. opentrons/calibration_storage/ot3/pipette_offset.py +95 -0
  22. opentrons/calibration_storage/types.py +45 -0
  23. opentrons/cli/__init__.py +21 -0
  24. opentrons/cli/__main__.py +5 -0
  25. opentrons/cli/analyze.py +557 -0
  26. opentrons/config/__init__.py +631 -0
  27. opentrons/config/advanced_settings.py +871 -0
  28. opentrons/config/defaults_ot2.py +214 -0
  29. opentrons/config/defaults_ot3.py +499 -0
  30. opentrons/config/feature_flags.py +86 -0
  31. opentrons/config/gripper_config.py +55 -0
  32. opentrons/config/reset.py +203 -0
  33. opentrons/config/robot_configs.py +187 -0
  34. opentrons/config/types.py +183 -0
  35. opentrons/drivers/__init__.py +0 -0
  36. opentrons/drivers/absorbance_reader/__init__.py +11 -0
  37. opentrons/drivers/absorbance_reader/abstract.py +72 -0
  38. opentrons/drivers/absorbance_reader/async_byonoy.py +352 -0
  39. opentrons/drivers/absorbance_reader/driver.py +81 -0
  40. opentrons/drivers/absorbance_reader/hid_protocol.py +161 -0
  41. opentrons/drivers/absorbance_reader/simulator.py +84 -0
  42. opentrons/drivers/asyncio/__init__.py +0 -0
  43. opentrons/drivers/asyncio/communication/__init__.py +22 -0
  44. opentrons/drivers/asyncio/communication/async_serial.py +187 -0
  45. opentrons/drivers/asyncio/communication/errors.py +88 -0
  46. opentrons/drivers/asyncio/communication/serial_connection.py +557 -0
  47. opentrons/drivers/command_builder.py +102 -0
  48. opentrons/drivers/flex_stacker/__init__.py +13 -0
  49. opentrons/drivers/flex_stacker/abstract.py +214 -0
  50. opentrons/drivers/flex_stacker/driver.py +768 -0
  51. opentrons/drivers/flex_stacker/errors.py +68 -0
  52. opentrons/drivers/flex_stacker/simulator.py +309 -0
  53. opentrons/drivers/flex_stacker/types.py +367 -0
  54. opentrons/drivers/flex_stacker/utils.py +19 -0
  55. opentrons/drivers/heater_shaker/__init__.py +5 -0
  56. opentrons/drivers/heater_shaker/abstract.py +76 -0
  57. opentrons/drivers/heater_shaker/driver.py +204 -0
  58. opentrons/drivers/heater_shaker/simulator.py +94 -0
  59. opentrons/drivers/mag_deck/__init__.py +6 -0
  60. opentrons/drivers/mag_deck/abstract.py +44 -0
  61. opentrons/drivers/mag_deck/driver.py +208 -0
  62. opentrons/drivers/mag_deck/simulator.py +63 -0
  63. opentrons/drivers/rpi_drivers/__init__.py +33 -0
  64. opentrons/drivers/rpi_drivers/dev_types.py +94 -0
  65. opentrons/drivers/rpi_drivers/gpio.py +282 -0
  66. opentrons/drivers/rpi_drivers/gpio_simulator.py +127 -0
  67. opentrons/drivers/rpi_drivers/interfaces.py +15 -0
  68. opentrons/drivers/rpi_drivers/types.py +364 -0
  69. opentrons/drivers/rpi_drivers/usb.py +102 -0
  70. opentrons/drivers/rpi_drivers/usb_simulator.py +22 -0
  71. opentrons/drivers/serial_communication.py +151 -0
  72. opentrons/drivers/smoothie_drivers/__init__.py +4 -0
  73. opentrons/drivers/smoothie_drivers/connection.py +51 -0
  74. opentrons/drivers/smoothie_drivers/constants.py +121 -0
  75. opentrons/drivers/smoothie_drivers/driver_3_0.py +1933 -0
  76. opentrons/drivers/smoothie_drivers/errors.py +49 -0
  77. opentrons/drivers/smoothie_drivers/parse_utils.py +143 -0
  78. opentrons/drivers/smoothie_drivers/simulator.py +99 -0
  79. opentrons/drivers/smoothie_drivers/types.py +16 -0
  80. opentrons/drivers/temp_deck/__init__.py +10 -0
  81. opentrons/drivers/temp_deck/abstract.py +54 -0
  82. opentrons/drivers/temp_deck/driver.py +197 -0
  83. opentrons/drivers/temp_deck/simulator.py +57 -0
  84. opentrons/drivers/thermocycler/__init__.py +12 -0
  85. opentrons/drivers/thermocycler/abstract.py +99 -0
  86. opentrons/drivers/thermocycler/driver.py +395 -0
  87. opentrons/drivers/thermocycler/simulator.py +126 -0
  88. opentrons/drivers/types.py +107 -0
  89. opentrons/drivers/utils.py +222 -0
  90. opentrons/execute.py +742 -0
  91. opentrons/hardware_control/__init__.py +65 -0
  92. opentrons/hardware_control/__main__.py +77 -0
  93. opentrons/hardware_control/adapters.py +98 -0
  94. opentrons/hardware_control/api.py +1347 -0
  95. opentrons/hardware_control/backends/__init__.py +7 -0
  96. opentrons/hardware_control/backends/controller.py +400 -0
  97. opentrons/hardware_control/backends/errors.py +9 -0
  98. opentrons/hardware_control/backends/estop_state.py +164 -0
  99. opentrons/hardware_control/backends/flex_protocol.py +497 -0
  100. opentrons/hardware_control/backends/ot3controller.py +1930 -0
  101. opentrons/hardware_control/backends/ot3simulator.py +900 -0
  102. opentrons/hardware_control/backends/ot3utils.py +664 -0
  103. opentrons/hardware_control/backends/simulator.py +442 -0
  104. opentrons/hardware_control/backends/status_bar_state.py +240 -0
  105. opentrons/hardware_control/backends/subsystem_manager.py +431 -0
  106. opentrons/hardware_control/backends/tip_presence_manager.py +173 -0
  107. opentrons/hardware_control/backends/types.py +14 -0
  108. opentrons/hardware_control/constants.py +6 -0
  109. opentrons/hardware_control/dev_types.py +125 -0
  110. opentrons/hardware_control/emulation/__init__.py +0 -0
  111. opentrons/hardware_control/emulation/abstract_emulator.py +21 -0
  112. opentrons/hardware_control/emulation/app.py +56 -0
  113. opentrons/hardware_control/emulation/connection_handler.py +38 -0
  114. opentrons/hardware_control/emulation/heater_shaker.py +150 -0
  115. opentrons/hardware_control/emulation/magdeck.py +60 -0
  116. opentrons/hardware_control/emulation/module_server/__init__.py +8 -0
  117. opentrons/hardware_control/emulation/module_server/client.py +78 -0
  118. opentrons/hardware_control/emulation/module_server/helpers.py +130 -0
  119. opentrons/hardware_control/emulation/module_server/models.py +31 -0
  120. opentrons/hardware_control/emulation/module_server/server.py +110 -0
  121. opentrons/hardware_control/emulation/parser.py +74 -0
  122. opentrons/hardware_control/emulation/proxy.py +241 -0
  123. opentrons/hardware_control/emulation/run_emulator.py +68 -0
  124. opentrons/hardware_control/emulation/scripts/__init__.py +0 -0
  125. opentrons/hardware_control/emulation/scripts/run_app.py +54 -0
  126. opentrons/hardware_control/emulation/scripts/run_module_emulator.py +72 -0
  127. opentrons/hardware_control/emulation/scripts/run_smoothie.py +37 -0
  128. opentrons/hardware_control/emulation/settings.py +119 -0
  129. opentrons/hardware_control/emulation/simulations.py +133 -0
  130. opentrons/hardware_control/emulation/smoothie.py +192 -0
  131. opentrons/hardware_control/emulation/tempdeck.py +69 -0
  132. opentrons/hardware_control/emulation/thermocycler.py +128 -0
  133. opentrons/hardware_control/emulation/types.py +10 -0
  134. opentrons/hardware_control/emulation/util.py +38 -0
  135. opentrons/hardware_control/errors.py +43 -0
  136. opentrons/hardware_control/execution_manager.py +164 -0
  137. opentrons/hardware_control/instruments/__init__.py +5 -0
  138. opentrons/hardware_control/instruments/instrument_abc.py +39 -0
  139. opentrons/hardware_control/instruments/ot2/__init__.py +0 -0
  140. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +152 -0
  141. opentrons/hardware_control/instruments/ot2/pipette.py +777 -0
  142. opentrons/hardware_control/instruments/ot2/pipette_handler.py +995 -0
  143. opentrons/hardware_control/instruments/ot3/__init__.py +0 -0
  144. opentrons/hardware_control/instruments/ot3/gripper.py +420 -0
  145. opentrons/hardware_control/instruments/ot3/gripper_handler.py +173 -0
  146. opentrons/hardware_control/instruments/ot3/instrument_calibration.py +214 -0
  147. opentrons/hardware_control/instruments/ot3/pipette.py +858 -0
  148. opentrons/hardware_control/instruments/ot3/pipette_handler.py +1030 -0
  149. opentrons/hardware_control/module_control.py +332 -0
  150. opentrons/hardware_control/modules/__init__.py +69 -0
  151. opentrons/hardware_control/modules/absorbance_reader.py +373 -0
  152. opentrons/hardware_control/modules/errors.py +7 -0
  153. opentrons/hardware_control/modules/flex_stacker.py +948 -0
  154. opentrons/hardware_control/modules/heater_shaker.py +426 -0
  155. opentrons/hardware_control/modules/lid_temp_status.py +35 -0
  156. opentrons/hardware_control/modules/magdeck.py +233 -0
  157. opentrons/hardware_control/modules/mod_abc.py +245 -0
  158. opentrons/hardware_control/modules/module_calibration.py +93 -0
  159. opentrons/hardware_control/modules/plate_temp_status.py +61 -0
  160. opentrons/hardware_control/modules/tempdeck.py +299 -0
  161. opentrons/hardware_control/modules/thermocycler.py +731 -0
  162. opentrons/hardware_control/modules/types.py +417 -0
  163. opentrons/hardware_control/modules/update.py +255 -0
  164. opentrons/hardware_control/modules/utils.py +73 -0
  165. opentrons/hardware_control/motion_utilities.py +318 -0
  166. opentrons/hardware_control/nozzle_manager.py +422 -0
  167. opentrons/hardware_control/ot3_calibration.py +1171 -0
  168. opentrons/hardware_control/ot3api.py +3227 -0
  169. opentrons/hardware_control/pause_manager.py +31 -0
  170. opentrons/hardware_control/poller.py +112 -0
  171. opentrons/hardware_control/protocols/__init__.py +106 -0
  172. opentrons/hardware_control/protocols/asyncio_configurable.py +11 -0
  173. opentrons/hardware_control/protocols/calibratable.py +45 -0
  174. opentrons/hardware_control/protocols/chassis_accessory_manager.py +90 -0
  175. opentrons/hardware_control/protocols/configurable.py +48 -0
  176. opentrons/hardware_control/protocols/event_sourcer.py +18 -0
  177. opentrons/hardware_control/protocols/execution_controllable.py +33 -0
  178. opentrons/hardware_control/protocols/flex_calibratable.py +96 -0
  179. opentrons/hardware_control/protocols/flex_instrument_configurer.py +52 -0
  180. opentrons/hardware_control/protocols/gripper_controller.py +55 -0
  181. opentrons/hardware_control/protocols/hardware_manager.py +51 -0
  182. opentrons/hardware_control/protocols/identifiable.py +16 -0
  183. opentrons/hardware_control/protocols/instrument_configurer.py +206 -0
  184. opentrons/hardware_control/protocols/liquid_handler.py +266 -0
  185. opentrons/hardware_control/protocols/module_provider.py +16 -0
  186. opentrons/hardware_control/protocols/motion_controller.py +243 -0
  187. opentrons/hardware_control/protocols/position_estimator.py +45 -0
  188. opentrons/hardware_control/protocols/simulatable.py +10 -0
  189. opentrons/hardware_control/protocols/stoppable.py +9 -0
  190. opentrons/hardware_control/protocols/types.py +27 -0
  191. opentrons/hardware_control/robot_calibration.py +224 -0
  192. opentrons/hardware_control/scripts/README.md +28 -0
  193. opentrons/hardware_control/scripts/__init__.py +1 -0
  194. opentrons/hardware_control/scripts/gripper_control.py +208 -0
  195. opentrons/hardware_control/scripts/ot3gripper +7 -0
  196. opentrons/hardware_control/scripts/ot3repl +7 -0
  197. opentrons/hardware_control/scripts/repl.py +187 -0
  198. opentrons/hardware_control/scripts/tc_control.py +97 -0
  199. opentrons/hardware_control/scripts/update_module_fw.py +274 -0
  200. opentrons/hardware_control/simulator_setup.py +260 -0
  201. opentrons/hardware_control/thread_manager.py +431 -0
  202. opentrons/hardware_control/threaded_async_lock.py +97 -0
  203. opentrons/hardware_control/types.py +792 -0
  204. opentrons/hardware_control/util.py +234 -0
  205. opentrons/legacy_broker.py +53 -0
  206. opentrons/legacy_commands/__init__.py +1 -0
  207. opentrons/legacy_commands/commands.py +483 -0
  208. opentrons/legacy_commands/helpers.py +153 -0
  209. opentrons/legacy_commands/module_commands.py +276 -0
  210. opentrons/legacy_commands/protocol_commands.py +54 -0
  211. opentrons/legacy_commands/publisher.py +155 -0
  212. opentrons/legacy_commands/robot_commands.py +51 -0
  213. opentrons/legacy_commands/types.py +1186 -0
  214. opentrons/motion_planning/__init__.py +32 -0
  215. opentrons/motion_planning/adjacent_slots_getters.py +168 -0
  216. opentrons/motion_planning/deck_conflict.py +501 -0
  217. opentrons/motion_planning/errors.py +35 -0
  218. opentrons/motion_planning/types.py +42 -0
  219. opentrons/motion_planning/waypoints.py +218 -0
  220. opentrons/ordered_set.py +138 -0
  221. opentrons/protocol_api/__init__.py +105 -0
  222. opentrons/protocol_api/_liquid.py +157 -0
  223. opentrons/protocol_api/_liquid_properties.py +814 -0
  224. opentrons/protocol_api/_nozzle_layout.py +31 -0
  225. opentrons/protocol_api/_parameter_context.py +300 -0
  226. opentrons/protocol_api/_parameters.py +31 -0
  227. opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
  228. opentrons/protocol_api/_types.py +43 -0
  229. opentrons/protocol_api/config.py +23 -0
  230. opentrons/protocol_api/core/__init__.py +23 -0
  231. opentrons/protocol_api/core/common.py +33 -0
  232. opentrons/protocol_api/core/core_map.py +74 -0
  233. opentrons/protocol_api/core/engine/__init__.py +22 -0
  234. opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
  235. opentrons/protocol_api/core/engine/deck_conflict.py +400 -0
  236. opentrons/protocol_api/core/engine/exceptions.py +19 -0
  237. opentrons/protocol_api/core/engine/instrument.py +2391 -0
  238. opentrons/protocol_api/core/engine/labware.py +238 -0
  239. opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
  240. opentrons/protocol_api/core/engine/module_core.py +1027 -0
  241. opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
  242. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
  243. opentrons/protocol_api/core/engine/point_calculations.py +64 -0
  244. opentrons/protocol_api/core/engine/protocol.py +1153 -0
  245. opentrons/protocol_api/core/engine/robot.py +139 -0
  246. opentrons/protocol_api/core/engine/stringify.py +74 -0
  247. opentrons/protocol_api/core/engine/transfer_components_executor.py +1006 -0
  248. opentrons/protocol_api/core/engine/well.py +241 -0
  249. opentrons/protocol_api/core/instrument.py +459 -0
  250. opentrons/protocol_api/core/labware.py +151 -0
  251. opentrons/protocol_api/core/legacy/__init__.py +11 -0
  252. opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
  253. opentrons/protocol_api/core/legacy/deck.py +369 -0
  254. opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
  255. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
  256. opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
  257. opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
  258. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
  259. opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
  260. opentrons/protocol_api/core/legacy/load_info.py +67 -0
  261. opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
  262. opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
  263. opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
  264. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
  265. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
  266. opentrons/protocol_api/core/module.py +484 -0
  267. opentrons/protocol_api/core/protocol.py +311 -0
  268. opentrons/protocol_api/core/robot.py +51 -0
  269. opentrons/protocol_api/core/well.py +116 -0
  270. opentrons/protocol_api/core/well_grid.py +45 -0
  271. opentrons/protocol_api/create_protocol_context.py +177 -0
  272. opentrons/protocol_api/deck.py +223 -0
  273. opentrons/protocol_api/disposal_locations.py +244 -0
  274. opentrons/protocol_api/instrument_context.py +3272 -0
  275. opentrons/protocol_api/labware.py +1579 -0
  276. opentrons/protocol_api/module_contexts.py +1447 -0
  277. opentrons/protocol_api/module_validation_and_errors.py +61 -0
  278. opentrons/protocol_api/protocol_context.py +1688 -0
  279. opentrons/protocol_api/robot_context.py +303 -0
  280. opentrons/protocol_api/validation.py +761 -0
  281. opentrons/protocol_engine/__init__.py +155 -0
  282. opentrons/protocol_engine/actions/__init__.py +65 -0
  283. opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
  284. opentrons/protocol_engine/actions/action_handler.py +13 -0
  285. opentrons/protocol_engine/actions/actions.py +302 -0
  286. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  287. opentrons/protocol_engine/clients/__init__.py +5 -0
  288. opentrons/protocol_engine/clients/sync_client.py +174 -0
  289. opentrons/protocol_engine/clients/transports.py +197 -0
  290. opentrons/protocol_engine/commands/__init__.py +757 -0
  291. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
  292. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
  293. opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
  294. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
  295. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
  296. opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
  297. opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
  298. opentrons/protocol_engine/commands/aspirate.py +244 -0
  299. opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
  300. opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
  301. opentrons/protocol_engine/commands/blow_out.py +146 -0
  302. opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
  303. opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
  304. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
  305. opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
  306. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
  307. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
  308. opentrons/protocol_engine/commands/command.py +308 -0
  309. opentrons/protocol_engine/commands/command_unions.py +974 -0
  310. opentrons/protocol_engine/commands/comment.py +57 -0
  311. opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
  312. opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
  313. opentrons/protocol_engine/commands/custom.py +67 -0
  314. opentrons/protocol_engine/commands/dispense.py +194 -0
  315. opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
  316. opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
  317. opentrons/protocol_engine/commands/drop_tip.py +232 -0
  318. opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
  319. opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
  320. opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
  321. opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
  322. opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
  323. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
  324. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
  325. opentrons/protocol_engine/commands/flex_stacker/store.py +339 -0
  326. opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
  327. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  328. opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
  329. opentrons/protocol_engine/commands/hash_command_params.py +38 -0
  330. opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
  331. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
  332. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
  333. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
  334. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
  335. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
  336. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
  337. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
  338. opentrons/protocol_engine/commands/home.py +100 -0
  339. opentrons/protocol_engine/commands/identify_module.py +86 -0
  340. opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
  341. opentrons/protocol_engine/commands/liquid_probe.py +464 -0
  342. opentrons/protocol_engine/commands/load_labware.py +210 -0
  343. opentrons/protocol_engine/commands/load_lid.py +154 -0
  344. opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
  345. opentrons/protocol_engine/commands/load_liquid.py +95 -0
  346. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  347. opentrons/protocol_engine/commands/load_module.py +223 -0
  348. opentrons/protocol_engine/commands/load_pipette.py +167 -0
  349. opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
  350. opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
  351. opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
  352. opentrons/protocol_engine/commands/move_labware.py +546 -0
  353. opentrons/protocol_engine/commands/move_relative.py +102 -0
  354. opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
  355. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
  356. opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
  357. opentrons/protocol_engine/commands/move_to_well.py +119 -0
  358. opentrons/protocol_engine/commands/movement_common.py +338 -0
  359. opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
  360. opentrons/protocol_engine/commands/pipetting_common.py +443 -0
  361. opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
  362. opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
  363. opentrons/protocol_engine/commands/reload_labware.py +90 -0
  364. opentrons/protocol_engine/commands/retract_axis.py +75 -0
  365. opentrons/protocol_engine/commands/robot/__init__.py +70 -0
  366. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -0
  367. opentrons/protocol_engine/commands/robot/common.py +18 -0
  368. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  369. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  370. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  371. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +86 -0
  372. opentrons/protocol_engine/commands/save_position.py +109 -0
  373. opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
  374. opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
  375. opentrons/protocol_engine/commands/set_status_bar.py +89 -0
  376. opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
  377. opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
  378. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
  379. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
  380. opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
  381. opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
  382. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
  383. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
  384. opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
  385. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
  386. opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
  387. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
  388. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
  389. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
  390. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
  391. opentrons/protocol_engine/commands/touch_tip.py +189 -0
  392. opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
  393. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
  394. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
  395. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
  396. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
  397. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
  398. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
  399. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
  400. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
  401. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
  402. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
  403. opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
  404. opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
  405. opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
  406. opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
  407. opentrons/protocol_engine/create_protocol_engine.py +193 -0
  408. opentrons/protocol_engine/engine_support.py +28 -0
  409. opentrons/protocol_engine/error_recovery_policy.py +81 -0
  410. opentrons/protocol_engine/errors/__init__.py +191 -0
  411. opentrons/protocol_engine/errors/error_occurrence.py +182 -0
  412. opentrons/protocol_engine/errors/exceptions.py +1308 -0
  413. opentrons/protocol_engine/execution/__init__.py +50 -0
  414. opentrons/protocol_engine/execution/command_executor.py +216 -0
  415. opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
  416. opentrons/protocol_engine/execution/door_watcher.py +119 -0
  417. opentrons/protocol_engine/execution/equipment.py +819 -0
  418. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  419. opentrons/protocol_engine/execution/gantry_mover.py +686 -0
  420. opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
  421. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
  422. opentrons/protocol_engine/execution/labware_movement.py +297 -0
  423. opentrons/protocol_engine/execution/movement.py +350 -0
  424. opentrons/protocol_engine/execution/pipetting.py +607 -0
  425. opentrons/protocol_engine/execution/queue_worker.py +86 -0
  426. opentrons/protocol_engine/execution/rail_lights.py +25 -0
  427. opentrons/protocol_engine/execution/run_control.py +33 -0
  428. opentrons/protocol_engine/execution/status_bar.py +34 -0
  429. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
  430. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
  431. opentrons/protocol_engine/execution/tip_handler.py +550 -0
  432. opentrons/protocol_engine/labware_offset_standardization.py +194 -0
  433. opentrons/protocol_engine/notes/__init__.py +17 -0
  434. opentrons/protocol_engine/notes/notes.py +59 -0
  435. opentrons/protocol_engine/plugins.py +104 -0
  436. opentrons/protocol_engine/protocol_engine.py +683 -0
  437. opentrons/protocol_engine/resources/__init__.py +26 -0
  438. opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
  439. opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
  440. opentrons/protocol_engine/resources/file_provider.py +161 -0
  441. opentrons/protocol_engine/resources/fixture_validation.py +68 -0
  442. opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
  443. opentrons/protocol_engine/resources/labware_validation.py +73 -0
  444. opentrons/protocol_engine/resources/model_utils.py +32 -0
  445. opentrons/protocol_engine/resources/module_data_provider.py +44 -0
  446. opentrons/protocol_engine/resources/ot3_validation.py +21 -0
  447. opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
  448. opentrons/protocol_engine/slot_standardization.py +128 -0
  449. opentrons/protocol_engine/state/__init__.py +1 -0
  450. opentrons/protocol_engine/state/_abstract_store.py +27 -0
  451. opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
  452. opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
  453. opentrons/protocol_engine/state/_move_types.py +83 -0
  454. opentrons/protocol_engine/state/_well_math.py +193 -0
  455. opentrons/protocol_engine/state/addressable_areas.py +699 -0
  456. opentrons/protocol_engine/state/command_history.py +309 -0
  457. opentrons/protocol_engine/state/commands.py +1164 -0
  458. opentrons/protocol_engine/state/config.py +39 -0
  459. opentrons/protocol_engine/state/files.py +57 -0
  460. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  461. opentrons/protocol_engine/state/geometry.py +2408 -0
  462. opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
  463. opentrons/protocol_engine/state/labware.py +1432 -0
  464. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  465. opentrons/protocol_engine/state/liquids.py +73 -0
  466. opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
  467. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
  468. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
  469. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
  470. opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
  471. opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
  472. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
  473. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
  474. opentrons/protocol_engine/state/modules.py +1515 -0
  475. opentrons/protocol_engine/state/motion.py +373 -0
  476. opentrons/protocol_engine/state/pipettes.py +905 -0
  477. opentrons/protocol_engine/state/state.py +421 -0
  478. opentrons/protocol_engine/state/state_summary.py +36 -0
  479. opentrons/protocol_engine/state/tips.py +420 -0
  480. opentrons/protocol_engine/state/update_types.py +904 -0
  481. opentrons/protocol_engine/state/wells.py +290 -0
  482. opentrons/protocol_engine/types/__init__.py +310 -0
  483. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  484. opentrons/protocol_engine/types/command_annotations.py +53 -0
  485. opentrons/protocol_engine/types/deck_configuration.py +81 -0
  486. opentrons/protocol_engine/types/execution.py +96 -0
  487. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  488. opentrons/protocol_engine/types/instrument.py +47 -0
  489. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  490. opentrons/protocol_engine/types/labware.py +131 -0
  491. opentrons/protocol_engine/types/labware_movement.py +22 -0
  492. opentrons/protocol_engine/types/labware_offset_location.py +111 -0
  493. opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
  494. opentrons/protocol_engine/types/liquid.py +40 -0
  495. opentrons/protocol_engine/types/liquid_class.py +59 -0
  496. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  497. opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
  498. opentrons/protocol_engine/types/location.py +194 -0
  499. opentrons/protocol_engine/types/module.py +310 -0
  500. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  501. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  502. opentrons/protocol_engine/types/tip.py +18 -0
  503. opentrons/protocol_engine/types/util.py +21 -0
  504. opentrons/protocol_engine/types/well_position.py +124 -0
  505. opentrons/protocol_reader/__init__.py +37 -0
  506. opentrons/protocol_reader/extract_labware_definitions.py +66 -0
  507. opentrons/protocol_reader/file_format_validator.py +152 -0
  508. opentrons/protocol_reader/file_hasher.py +27 -0
  509. opentrons/protocol_reader/file_identifier.py +284 -0
  510. opentrons/protocol_reader/file_reader_writer.py +90 -0
  511. opentrons/protocol_reader/input_file.py +16 -0
  512. opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
  513. opentrons/protocol_reader/protocol_reader.py +188 -0
  514. opentrons/protocol_reader/protocol_source.py +124 -0
  515. opentrons/protocol_reader/role_analyzer.py +86 -0
  516. opentrons/protocol_runner/__init__.py +26 -0
  517. opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
  518. opentrons/protocol_runner/json_file_reader.py +55 -0
  519. opentrons/protocol_runner/json_translator.py +314 -0
  520. opentrons/protocol_runner/legacy_command_mapper.py +852 -0
  521. opentrons/protocol_runner/legacy_context_plugin.py +116 -0
  522. opentrons/protocol_runner/protocol_runner.py +530 -0
  523. opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
  524. opentrons/protocol_runner/run_orchestrator.py +496 -0
  525. opentrons/protocol_runner/task_queue.py +95 -0
  526. opentrons/protocols/__init__.py +6 -0
  527. opentrons/protocols/advanced_control/__init__.py +0 -0
  528. opentrons/protocols/advanced_control/common.py +38 -0
  529. opentrons/protocols/advanced_control/mix.py +60 -0
  530. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  531. opentrons/protocols/advanced_control/transfers/common.py +180 -0
  532. opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
  533. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
  534. opentrons/protocols/api_support/__init__.py +0 -0
  535. opentrons/protocols/api_support/constants.py +8 -0
  536. opentrons/protocols/api_support/deck_type.py +110 -0
  537. opentrons/protocols/api_support/definitions.py +18 -0
  538. opentrons/protocols/api_support/instrument.py +151 -0
  539. opentrons/protocols/api_support/labware_like.py +233 -0
  540. opentrons/protocols/api_support/tip_tracker.py +175 -0
  541. opentrons/protocols/api_support/types.py +32 -0
  542. opentrons/protocols/api_support/util.py +403 -0
  543. opentrons/protocols/bundle.py +89 -0
  544. opentrons/protocols/duration/__init__.py +4 -0
  545. opentrons/protocols/duration/errors.py +5 -0
  546. opentrons/protocols/duration/estimator.py +628 -0
  547. opentrons/protocols/execution/__init__.py +0 -0
  548. opentrons/protocols/execution/dev_types.py +181 -0
  549. opentrons/protocols/execution/errors.py +40 -0
  550. opentrons/protocols/execution/execute.py +84 -0
  551. opentrons/protocols/execution/execute_json_v3.py +275 -0
  552. opentrons/protocols/execution/execute_json_v4.py +359 -0
  553. opentrons/protocols/execution/execute_json_v5.py +28 -0
  554. opentrons/protocols/execution/execute_python.py +169 -0
  555. opentrons/protocols/execution/json_dispatchers.py +87 -0
  556. opentrons/protocols/execution/types.py +7 -0
  557. opentrons/protocols/geometry/__init__.py +0 -0
  558. opentrons/protocols/geometry/planning.py +297 -0
  559. opentrons/protocols/labware.py +312 -0
  560. opentrons/protocols/models/__init__.py +0 -0
  561. opentrons/protocols/models/json_protocol.py +679 -0
  562. opentrons/protocols/parameters/__init__.py +0 -0
  563. opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
  564. opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
  565. opentrons/protocols/parameters/exceptions.py +34 -0
  566. opentrons/protocols/parameters/parameter_definition.py +272 -0
  567. opentrons/protocols/parameters/types.py +17 -0
  568. opentrons/protocols/parameters/validation.py +267 -0
  569. opentrons/protocols/parse.py +671 -0
  570. opentrons/protocols/types.py +159 -0
  571. opentrons/py.typed +0 -0
  572. opentrons/resources/scripts/lpc21isp +0 -0
  573. opentrons/resources/smoothie-edge-8414642.hex +23010 -0
  574. opentrons/simulate.py +1065 -0
  575. opentrons/system/__init__.py +6 -0
  576. opentrons/system/camera.py +51 -0
  577. opentrons/system/log_control.py +59 -0
  578. opentrons/system/nmcli.py +856 -0
  579. opentrons/system/resin.py +24 -0
  580. opentrons/system/smoothie_update.py +15 -0
  581. opentrons/system/wifi.py +204 -0
  582. opentrons/tools/__init__.py +0 -0
  583. opentrons/tools/args_handler.py +22 -0
  584. opentrons/tools/write_pipette_memory.py +157 -0
  585. opentrons/types.py +618 -0
  586. opentrons/util/__init__.py +1 -0
  587. opentrons/util/async_helpers.py +166 -0
  588. opentrons/util/broker.py +84 -0
  589. opentrons/util/change_notifier.py +47 -0
  590. opentrons/util/entrypoint_util.py +278 -0
  591. opentrons/util/get_union_elements.py +26 -0
  592. opentrons/util/helpers.py +6 -0
  593. opentrons/util/linal.py +178 -0
  594. opentrons/util/logging_config.py +265 -0
  595. opentrons/util/logging_queue_handler.py +61 -0
  596. opentrons/util/performance_helpers.py +157 -0
  597. opentrons-8.6.0.dist-info/METADATA +37 -0
  598. opentrons-8.6.0.dist-info/RECORD +601 -0
  599. opentrons-8.6.0.dist-info/WHEEL +4 -0
  600. opentrons-8.6.0.dist-info/entry_points.txt +3 -0
  601. opentrons-8.6.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,1164 @@
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
+ if (
384
+ self._state.queue_status == QueueStatus.PAUSED
385
+ or self._state.is_door_blocking
386
+ ):
387
+ self._state.queue_status = QueueStatus.AWAITING_RECOVERY_PAUSED
388
+ else:
389
+ self._state.queue_status = QueueStatus.AWAITING_RECOVERY
390
+ self._state.recovery_target = _RecoveryTargetInfo(
391
+ command_id=action.command_id,
392
+ state_update_if_false_positive=state_update_if_false_positive,
393
+ )
394
+ self._state.has_entered_error_recovery = True
395
+
396
+ # When one command fails, we generally also cancel the commands that
397
+ # would have been queued after it.
398
+ other_command_ids_to_fail: List[str]
399
+ if prev_entry.command.intent == CommandIntent.SETUP:
400
+ other_command_ids_to_fail = list(
401
+ self._state.command_history.get_setup_queue_ids()
402
+ )
403
+ elif prev_entry.command.intent == CommandIntent.FIXIT:
404
+ if (
405
+ action.type == ErrorRecoveryType.CONTINUE_WITH_ERROR
406
+ or action.type == ErrorRecoveryType.ASSUME_FALSE_POSITIVE_AND_CONTINUE
407
+ ):
408
+ other_command_ids_to_fail = []
409
+ else:
410
+ other_command_ids_to_fail = list(
411
+ self._state.command_history.get_fixit_queue_ids()
412
+ )
413
+ elif (
414
+ prev_entry.command.intent == CommandIntent.PROTOCOL
415
+ or prev_entry.command.intent is None
416
+ ):
417
+ if action.type == ErrorRecoveryType.FAIL_RUN:
418
+ other_command_ids_to_fail = list(
419
+ self._state.command_history.get_queue_ids()
420
+ )
421
+ elif (
422
+ action.type == ErrorRecoveryType.WAIT_FOR_RECOVERY
423
+ or action.type == ErrorRecoveryType.CONTINUE_WITH_ERROR
424
+ or action.type == ErrorRecoveryType.ASSUME_FALSE_POSITIVE_AND_CONTINUE
425
+ ):
426
+ other_command_ids_to_fail = []
427
+ else:
428
+ assert_never(action.type)
429
+ else:
430
+ assert_never(prev_entry.command.intent)
431
+ for command_id in other_command_ids_to_fail:
432
+ # TODO(mc, 2022-06-06): add new "cancelled" status or similar
433
+ self._update_to_failed(
434
+ command_id=command_id,
435
+ failed_at=action.failed_at,
436
+ error_occurrence=None,
437
+ error_recovery_type=None,
438
+ notes=None,
439
+ )
440
+
441
+ def _handle_play_action(self, action: PlayAction) -> None:
442
+ if not self._state.run_result:
443
+ self._state.run_started_at = (
444
+ self._state.run_started_at or action.requested_at
445
+ )
446
+ match self._state.queue_status:
447
+ case QueueStatus.SETUP:
448
+ self._state.queue_status = (
449
+ QueueStatus.PAUSED
450
+ if self._state.is_door_blocking
451
+ else QueueStatus.RUNNING
452
+ )
453
+ case QueueStatus.AWAITING_RECOVERY_PAUSED:
454
+ self._state.queue_status = QueueStatus.AWAITING_RECOVERY
455
+ case QueueStatus.PAUSED:
456
+ self._state.queue_status = QueueStatus.RUNNING
457
+ case QueueStatus.RUNNING | QueueStatus.AWAITING_RECOVERY:
458
+ # Nothing for the play action to do. No-op.
459
+ pass
460
+
461
+ def _handle_pause_action(self, action: PauseAction) -> None:
462
+ self._state.queue_status = QueueStatus.PAUSED
463
+
464
+ def _handle_resume_from_recovery_action(
465
+ self, action: ResumeFromRecoveryAction
466
+ ) -> None:
467
+ self._state.queue_status = QueueStatus.RUNNING
468
+ self._state.recovery_target = None
469
+
470
+ def _handle_stop_action(self, action: StopAction) -> None:
471
+ if not self._state.run_result:
472
+ self._state.recovery_target = None
473
+ self._state.queue_status = QueueStatus.PAUSED
474
+
475
+ if action.from_estop:
476
+ self._state.stopped_by_estop = True
477
+ self._state.run_result = RunResult.FAILED
478
+ else:
479
+ self._state.run_result = RunResult.STOPPED
480
+
481
+ def _handle_finish_action(self, action: FinishAction) -> None:
482
+ if not self._state.run_result:
483
+ self._state.recovery_target = None
484
+ self._state.queue_status = QueueStatus.PAUSED
485
+
486
+ if action.set_run_status:
487
+ self._state.run_result = (
488
+ RunResult.SUCCEEDED
489
+ if not action.error_details
490
+ else RunResult.FAILED
491
+ )
492
+ else:
493
+ self._state.run_result = RunResult.STOPPED
494
+
495
+ if not self._state.run_error and action.error_details:
496
+ self._state.run_error = self._map_run_exception_to_error_occurrence(
497
+ action.error_details.error_id,
498
+ action.error_details.created_at,
499
+ action.error_details.error,
500
+ )
501
+ else:
502
+ # HACK(sf): There needs to be a better way to set
503
+ # an estop error than this else clause
504
+ if self._state.stopped_by_estop and action.error_details:
505
+ self._state.run_error = self._map_run_exception_to_error_occurrence(
506
+ action.error_details.error_id,
507
+ action.error_details.created_at,
508
+ action.error_details.error,
509
+ )
510
+
511
+ def _handle_hardware_stopped_action(self, action: HardwareStoppedAction) -> None:
512
+ self._state.queue_status = QueueStatus.PAUSED
513
+ self._state.run_result = self._state.run_result or RunResult.STOPPED
514
+ self._state.run_completed_at = (
515
+ self._state.run_completed_at or action.completed_at
516
+ )
517
+
518
+ if action.finish_error_details:
519
+ self._state.finish_error = self._map_finish_exception_to_error_occurrence(
520
+ action.finish_error_details.error_id,
521
+ action.finish_error_details.created_at,
522
+ action.finish_error_details.error,
523
+ )
524
+
525
+ def _handle_door_change_action(self, action: DoorChangeAction) -> None:
526
+ if self._config.block_on_door_open:
527
+ if action.door_state == DoorState.OPEN:
528
+ self._state.is_door_blocking = True
529
+ match self._state.queue_status:
530
+ case QueueStatus.SETUP:
531
+ pass
532
+ case QueueStatus.RUNNING | QueueStatus.PAUSED:
533
+ self._state.queue_status = QueueStatus.PAUSED
534
+ case (
535
+ QueueStatus.AWAITING_RECOVERY
536
+ | QueueStatus.AWAITING_RECOVERY_PAUSED
537
+ ):
538
+ self._state.queue_status = QueueStatus.AWAITING_RECOVERY_PAUSED
539
+ elif action.door_state == DoorState.CLOSED:
540
+ self._state.is_door_blocking = False
541
+
542
+ def _handle_set_error_recovery_policy_action(
543
+ self, action: SetErrorRecoveryPolicyAction
544
+ ) -> None:
545
+ self._state.error_recovery_policy = action.error_recovery_policy
546
+
547
+ def _update_to_failed(
548
+ self,
549
+ command_id: str,
550
+ failed_at: datetime,
551
+ error_occurrence: Optional[ErrorOccurrence],
552
+ error_recovery_type: Optional[ErrorRecoveryType],
553
+ notes: Optional[List[CommandNote]],
554
+ ) -> None:
555
+ prev_entry = self._state.command_history.get(command_id)
556
+ failed_command = prev_entry.command.model_copy(
557
+ update={
558
+ "completedAt": failed_at,
559
+ "status": CommandStatus.FAILED,
560
+ **({"error": error_occurrence} if error_occurrence is not None else {}),
561
+ # Assume we're not overwriting any existing notes because they can
562
+ # only be added when a command completes, and if we're failing this
563
+ # command, it wouldn't have completed before now.
564
+ **({"notes": notes} if notes is not None else {}),
565
+ }
566
+ )
567
+ self._state.command_history.set_command_failed(failed_command)
568
+ if error_recovery_type is not None:
569
+ self._state.command_error_recovery_types[command_id] = error_recovery_type
570
+
571
+ @staticmethod
572
+ def _map_run_exception_to_error_occurrence(
573
+ error_id: str, created_at: datetime, exception: Exception
574
+ ) -> ErrorOccurrence:
575
+ """Map a fatal exception from the main part of the run to an ErrorOccurrence."""
576
+ if (
577
+ isinstance(exception, ProtocolCommandFailedError)
578
+ and exception.original_error is not None
579
+ ):
580
+ return exception.original_error
581
+ elif isinstance(exception, EnumeratedError):
582
+ return ErrorOccurrence.from_failed(
583
+ id=error_id, createdAt=created_at, error=exception
584
+ )
585
+ else:
586
+ enumerated_wrapper = UnexpectedProtocolError(
587
+ message=str(exception),
588
+ wrapping=[exception],
589
+ )
590
+ return ErrorOccurrence.from_failed(
591
+ id=error_id, createdAt=created_at, error=enumerated_wrapper
592
+ )
593
+
594
+ @staticmethod
595
+ def _map_finish_exception_to_error_occurrence(
596
+ error_id: str, created_at: datetime, exception: Exception
597
+ ) -> ErrorOccurrence:
598
+ """Map a fatal exception from the finish phase (drop tip & home) to an ErrorOccurrence."""
599
+ if isinstance(exception, EnumeratedError):
600
+ return ErrorOccurrence.from_failed(
601
+ id=error_id, createdAt=created_at, error=exception
602
+ )
603
+ else:
604
+ enumerated_wrapper = PythonException(exc=exception)
605
+ return ErrorOccurrence.from_failed(
606
+ id=error_id, createdAt=created_at, error=enumerated_wrapper
607
+ )
608
+
609
+
610
+ class CommandView:
611
+ """Read-only command state view."""
612
+
613
+ _state: CommandState
614
+
615
+ def __init__(self, state: CommandState) -> None:
616
+ """Initialize the view of command state with its underlying data."""
617
+ self._state = state
618
+
619
+ def get(self, command_id: str) -> Command:
620
+ """Get a command by its unique identifier."""
621
+ return self._state.command_history.get(command_id).command
622
+
623
+ def get_all(self) -> List[Command]:
624
+ """Get a list of all commands in state.
625
+
626
+ Entries are returned in the order of first-added command to last-added command.
627
+ Replacing a command (to change its status, for example) keeps its place in the
628
+ ordering.
629
+ """
630
+ return self._state.command_history.get_all_commands()
631
+
632
+ def get_slice(
633
+ self, cursor: Optional[int], length: int, include_fixit_commands: bool
634
+ ) -> CommandSlice:
635
+ """Get a subset of commands around a given cursor.
636
+
637
+ If the cursor is omitted, a cursor will be selected automatically
638
+ based on the currently running or most recently executed command,
639
+ and the slice of commands returned is the previous `length` commands
640
+ inclusive of the currently running or most recently executed command.
641
+ """
642
+ command_ids = self._state.command_history.get_filtered_command_ids(
643
+ include_fixit_commands=include_fixit_commands
644
+ )
645
+ total_length = len(command_ids)
646
+
647
+ if cursor is None:
648
+ current_pointer = self.get_current()
649
+
650
+ if current_pointer is not None:
651
+ cursor = current_pointer.index
652
+ else:
653
+ cursor = total_length - 1
654
+
655
+ cursor = max(cursor - length + 1, 0)
656
+
657
+ # start is inclusive, stop is exclusive
658
+ start = max(0, min(cursor, total_length - 1))
659
+ stop = min(total_length, start + length)
660
+ commands = self._state.command_history.get_slice(
661
+ start=start, stop=stop, command_ids=command_ids
662
+ )
663
+
664
+ return CommandSlice(
665
+ commands=commands,
666
+ cursor=start,
667
+ total_length=total_length,
668
+ )
669
+
670
+ def get_errors_slice(
671
+ self,
672
+ cursor: int,
673
+ length: int,
674
+ ) -> CommandErrorSlice:
675
+ """Get a subset of commands error around a given cursor."""
676
+ # start is inclusive, stop is exclusive
677
+ all_errors = self.get_all_errors()
678
+ total_length = len(all_errors)
679
+ actual_cursor = max(0, min(cursor, total_length - 1))
680
+ stop = min(total_length, actual_cursor + length)
681
+
682
+ sliced_errors = all_errors[actual_cursor:stop]
683
+
684
+ return CommandErrorSlice(
685
+ commands_errors=sliced_errors,
686
+ cursor=actual_cursor,
687
+ total_length=total_length,
688
+ )
689
+
690
+ def get_error(self) -> Optional[ErrorOccurrence]:
691
+ """Get the run's fatal error, if there was one."""
692
+ run_error = self._state.run_error
693
+ finish_error = self._state.finish_error
694
+
695
+ if run_error and finish_error:
696
+ combined_error = ErrorOccurrence(
697
+ id=finish_error.id,
698
+ createdAt=finish_error.createdAt,
699
+ errorType="RunAndFinishFailed",
700
+ detail=(
701
+ "The run had a fatal error,"
702
+ " and another error happened while doing post-run cleanup."
703
+ ),
704
+ # TODO(mm, 2023-07-31): Consider adding a low-priority error code so clients can
705
+ # deemphasize this root node, in favor of its children in wrappedErrors.
706
+ errorCode=ErrorCodes.GENERAL_ERROR.value.code,
707
+ wrappedErrors=[
708
+ run_error,
709
+ finish_error,
710
+ ],
711
+ )
712
+ return combined_error
713
+ else:
714
+ return run_error or finish_error
715
+
716
+ def get_all_errors(self) -> List[ErrorOccurrence]:
717
+ """Get the run's full error list, if there was none, returns an empty list."""
718
+ failed_commands = self._state.command_history.get_all_failed_commands()
719
+ return [
720
+ command_error.error
721
+ for command_error in failed_commands
722
+ if command_error.error is not None
723
+ ]
724
+
725
+ def get_has_entered_recovery_mode(self) -> bool:
726
+ """Get whether the run has entered recovery mode."""
727
+ return self._state.has_entered_error_recovery
728
+
729
+ def get_running_command_id(self) -> Optional[str]:
730
+ """Return the ID of the command that's currently running, if there is one."""
731
+ running_command = self._state.command_history.get_running_command()
732
+ if running_command is not None:
733
+ return running_command.command.id
734
+ else:
735
+ return None
736
+
737
+ def get_queue_ids(self) -> OrderedSet[str]:
738
+ """Get the IDs of all queued protocol commands, in FIFO order."""
739
+ return self._state.command_history.get_queue_ids()
740
+
741
+ def get_current(self) -> Optional[CommandPointer]:
742
+ """Return the "current" command, if any.
743
+
744
+ The "current" command is the command that is currently executing,
745
+ or the most recent command to have completed.
746
+ """
747
+ running_command = self._state.command_history.get_running_command()
748
+ if running_command:
749
+ return CommandPointer(
750
+ command_id=running_command.command.id,
751
+ command_key=running_command.command.key,
752
+ created_at=running_command.command.createdAt,
753
+ index=running_command.index,
754
+ )
755
+
756
+ most_recently_finalized_command = self.get_most_recently_finalized_command()
757
+ if most_recently_finalized_command:
758
+ return CommandPointer(
759
+ command_id=most_recently_finalized_command.command.id,
760
+ command_key=most_recently_finalized_command.command.key,
761
+ created_at=most_recently_finalized_command.command.createdAt,
762
+ index=most_recently_finalized_command.index,
763
+ )
764
+
765
+ return None
766
+
767
+ def get_next_to_execute(self) -> Optional[str]:
768
+ """Return the next command in line to be executed.
769
+
770
+ Returns:
771
+ The ID of the earliest queued command, if any.
772
+
773
+ Raises:
774
+ RunStoppedError: The engine is currently stopped or stopping,
775
+ so it will never run any more commands.
776
+ """
777
+ if self._state.run_result:
778
+ raise RunStoppedError("Engine was stopped")
779
+
780
+ # if queue is in recovery mode, return the next fixit command
781
+ next_fixit_cmd = self._state.command_history.get_fixit_queue_ids().head(None)
782
+ if next_fixit_cmd and self._state.queue_status == QueueStatus.AWAITING_RECOVERY:
783
+ return next_fixit_cmd
784
+ if (
785
+ next_fixit_cmd
786
+ and self._state.queue_status == QueueStatus.AWAITING_RECOVERY_PAUSED
787
+ and self._may_run_with_door_open(fixit_command=self.get(next_fixit_cmd))
788
+ ):
789
+ return next_fixit_cmd
790
+
791
+ # if there is a setup command queued, prioritize it
792
+ next_setup_cmd = self._state.command_history.get_setup_queue_ids().head(None)
793
+ if (
794
+ self._state.queue_status
795
+ not in [QueueStatus.PAUSED, QueueStatus.AWAITING_RECOVERY]
796
+ and next_setup_cmd
797
+ ):
798
+ return next_setup_cmd
799
+
800
+ # if the queue is running, return the next protocol command
801
+ if self._state.queue_status == QueueStatus.RUNNING:
802
+ return self._state.command_history.get_queue_ids().head(None)
803
+
804
+ # otherwise we've got nothing to do
805
+ return None
806
+
807
+ def get_is_okay_to_clear(self) -> bool:
808
+ """Get whether the engine is stopped or sitting idly so it could be removed."""
809
+ if self.get_is_stopped():
810
+ return True
811
+ elif (
812
+ self.get_status() == EngineStatus.IDLE
813
+ and self._state.command_history.get_running_command() is None
814
+ and len(self._state.command_history.get_setup_queue_ids()) == 0
815
+ ):
816
+ return True
817
+ else:
818
+ return False
819
+
820
+ def get_is_door_blocking(self) -> bool:
821
+ """Get whether the robot door is open when 'pause on door open' ff is True."""
822
+ return self._state.is_door_blocking
823
+
824
+ def get_is_running(self) -> bool:
825
+ """Get whether the protocol is running & queued commands should be executed."""
826
+ return self._state.queue_status == QueueStatus.RUNNING
827
+
828
+ def get_most_recently_finalized_command(self) -> Optional[CommandEntry]:
829
+ """Get the most recent command that has reached its final `status`. See get_command_is_final."""
830
+ run_requested_to_stop = self._state.run_result is not None
831
+
832
+ if run_requested_to_stop:
833
+ tail_command = self._state.command_history.get_tail_command()
834
+ if not tail_command:
835
+ return None
836
+ if tail_command.command.status != CommandStatus.RUNNING:
837
+ return tail_command
838
+ else:
839
+ return self._state.command_history.get_prev(tail_command.command.id)
840
+ else:
841
+ most_recently_finalized = (
842
+ self._state.command_history.get_most_recently_completed_command()
843
+ )
844
+ # This iteration is effectively O(1) as we'll only ever have to iterate one or two times at most.
845
+ while most_recently_finalized is not None:
846
+ next_command = self._state.command_history.get_next(
847
+ most_recently_finalized.command.id
848
+ )
849
+ if (
850
+ next_command is not None
851
+ and next_command.command.status != CommandStatus.QUEUED
852
+ and next_command.command.status != CommandStatus.RUNNING
853
+ ):
854
+ most_recently_finalized = next_command
855
+ else:
856
+ break
857
+
858
+ return most_recently_finalized
859
+
860
+ def get_command_is_final(self, command_id: str) -> bool:
861
+ """Get whether a given command has reached its final `status`.
862
+
863
+ This happens when one of the following is true:
864
+
865
+ - Its status is `CommandStatus.SUCCEEDED`.
866
+ - Its status is `CommandStatus.FAILED`.
867
+ - Its status is `CommandStatus.QUEUED` but the run has been requested to stop,
868
+ so the run will never reach it.
869
+
870
+ Arguments:
871
+ command_id: Command to check.
872
+ """
873
+ status = self.get(command_id).status
874
+
875
+ run_requested_to_stop = self._state.run_result is not None
876
+
877
+ return (
878
+ status == CommandStatus.SUCCEEDED
879
+ or status == CommandStatus.FAILED
880
+ or (status == CommandStatus.QUEUED and run_requested_to_stop)
881
+ )
882
+
883
+ def get_all_commands_final(self) -> bool:
884
+ """Get whether all commands added so far have reached their final `status`.
885
+
886
+ See `get_command_is_final()`.
887
+
888
+ Raises:
889
+ CommandExecutionFailedError: if any added command failed, and its `intent` wasn't
890
+ `setup`.
891
+ """
892
+ no_command_running = self._state.command_history.get_running_command() is None
893
+ run_requested_to_stop = self._state.run_result is not None
894
+ no_command_to_execute = (
895
+ run_requested_to_stop
896
+ # TODO(mm, 2024-03-15): This ignores queued setup commands,
897
+ # which seems questionable?
898
+ or len(self._state.command_history.get_queue_ids()) == 0
899
+ )
900
+
901
+ return no_command_running and no_command_to_execute
902
+
903
+ def get_recovery_target(self) -> Optional[CommandPointer]:
904
+ """Return the command currently undergoing error recovery, if any."""
905
+ recovery_target = self._state.recovery_target
906
+ if recovery_target is None:
907
+ return None
908
+ else:
909
+ entry = self._state.command_history.get(recovery_target.command_id)
910
+ return CommandPointer(
911
+ command_id=entry.command.id,
912
+ command_key=entry.command.key,
913
+ created_at=entry.command.createdAt,
914
+ index=entry.index,
915
+ )
916
+
917
+ def get_recovery_in_progress_for_command(self, command_id: str) -> bool:
918
+ """Return whether the given command failed and its error recovery is in progress."""
919
+ pointer = self.get_recovery_target()
920
+ return pointer is not None and pointer.command_id == command_id
921
+
922
+ def raise_fatal_command_error(self) -> None:
923
+ """Raise the run's fatal command error, if there was one, as an exception.
924
+
925
+ The "fatal command error" is the error from any non-setup command.
926
+ It's intended to be used as the fatal error of the overall run
927
+ (see `ProtocolEngine.finish()`) for JSON and live HTTP protocols.
928
+
929
+ This isn't useful for Python protocols, which have to account for the
930
+ fatal error of the overall run coming from anywhere in the Python script,
931
+ including in between commands.
932
+ """
933
+ failed_command = self._state.failed_command
934
+ if (
935
+ failed_command
936
+ and failed_command.command.error
937
+ and failed_command.command.intent != CommandIntent.SETUP
938
+ ):
939
+ raise ProtocolCommandFailedError(
940
+ original_error=failed_command.command.error,
941
+ message=failed_command.command.error.detail,
942
+ )
943
+
944
+ def get_error_recovery_type(self, command_id: str) -> ErrorRecoveryType:
945
+ """Return the error recovery type with which the given command failed.
946
+
947
+ The command ID is assumed to point to a failed command.
948
+ """
949
+ return self._state.command_error_recovery_types[command_id]
950
+
951
+ def get_is_stopped(self) -> bool:
952
+ """Get whether an engine stop has completed."""
953
+ return self._state.run_completed_at is not None
954
+
955
+ def get_is_stopped_by_estop(self) -> bool:
956
+ """Return whether the engine was stopped specifically by an E-stop."""
957
+ return self._state.stopped_by_estop
958
+
959
+ def has_been_played(self) -> bool:
960
+ """Get whether engine has started."""
961
+ return self._state.run_started_at is not None
962
+
963
+ def get_is_terminal(self) -> bool:
964
+ """Get whether engine is in a terminal state."""
965
+ return self._state.run_result is not None
966
+
967
+ def validate_action_allowed( # noqa: C901
968
+ self,
969
+ action: Union[
970
+ PlayAction,
971
+ PauseAction,
972
+ StopAction,
973
+ ResumeFromRecoveryAction,
974
+ QueueCommandAction,
975
+ ],
976
+ ) -> Union[
977
+ PlayAction,
978
+ PauseAction,
979
+ StopAction,
980
+ ResumeFromRecoveryAction,
981
+ QueueCommandAction,
982
+ ]:
983
+ """Validate whether a given control action is allowed.
984
+
985
+ Returns:
986
+ The action, if valid.
987
+
988
+ Raises:
989
+ RunStoppedError: The engine has been stopped.
990
+ RobotDoorOpenError: Cannot resume because the front door is open.
991
+ PauseNotAllowedError: The engine is not running, so cannot be paused.
992
+ SetupCommandNotAllowedError: The engine is running, so a setup command
993
+ may not be added.
994
+ """
995
+ if self._state.run_result is not None:
996
+ raise RunStoppedError("The run has already stopped.")
997
+
998
+ elif isinstance(action, PlayAction):
999
+ if self.get_status() in (
1000
+ EngineStatus.BLOCKED_BY_OPEN_DOOR,
1001
+ EngineStatus.AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR,
1002
+ ):
1003
+ raise RobotDoorOpenError("Front door or top window is currently open.")
1004
+ else:
1005
+ return action
1006
+
1007
+ elif isinstance(action, PauseAction):
1008
+ if not self.get_is_running():
1009
+ raise PauseNotAllowedError("Cannot pause a run that is not running.")
1010
+ elif self.get_status() == EngineStatus.AWAITING_RECOVERY:
1011
+ raise PauseNotAllowedError("Cannot pause a run in recovery mode.")
1012
+ else:
1013
+ return action
1014
+
1015
+ elif isinstance(action, QueueCommandAction):
1016
+ if (
1017
+ action.request.intent == CommandIntent.SETUP
1018
+ and self._state.queue_status != QueueStatus.SETUP
1019
+ ):
1020
+ raise SetupCommandNotAllowedError(
1021
+ "Setup commands are not allowed after run has started."
1022
+ )
1023
+ elif action.request.intent == CommandIntent.FIXIT:
1024
+ if self.get_status() == EngineStatus.AWAITING_RECOVERY:
1025
+ return action
1026
+ elif self.get_status() in (
1027
+ EngineStatus.AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR,
1028
+ EngineStatus.AWAITING_RECOVERY_PAUSED,
1029
+ ):
1030
+ if self._may_run_with_door_open(fixit_command=action.request):
1031
+ return action
1032
+ else:
1033
+ raise FixitCommandNotAllowedError(
1034
+ f"{action.request.commandType} fixit command may not run"
1035
+ " until the door is closed and the run is played again."
1036
+ )
1037
+ else:
1038
+ raise FixitCommandNotAllowedError(
1039
+ "Fixit commands are not allowed when the run is not in a recoverable state."
1040
+ )
1041
+ else:
1042
+ return action
1043
+
1044
+ elif isinstance(action, ResumeFromRecoveryAction):
1045
+ if self.get_status() != EngineStatus.AWAITING_RECOVERY:
1046
+ raise ResumeFromRecoveryNotAllowedError(
1047
+ "Cannot resume from recovery if the run is not in recovery mode."
1048
+ )
1049
+ elif (
1050
+ self.get_status() == EngineStatus.AWAITING_RECOVERY
1051
+ and len(self._state.command_history.get_fixit_queue_ids()) > 0
1052
+ ):
1053
+ raise ResumeFromRecoveryNotAllowedError(
1054
+ "Cannot resume from recovery while there are fixit commands in the queue."
1055
+ )
1056
+ else:
1057
+ return action
1058
+
1059
+ elif isinstance(action, StopAction):
1060
+ return action
1061
+
1062
+ else:
1063
+ assert_never(action)
1064
+
1065
+ def get_status(self) -> EngineStatus: # noqa: C901
1066
+ """Get the current execution status of the engine."""
1067
+ if self._state.run_result:
1068
+ # The main part of the run is over, or will be over soon.
1069
+ # Have we also completed the post-run finish steps (homing and dropping tips)?
1070
+ if self.get_is_stopped():
1071
+ # Post-run finish steps have completed. Calculate the engine's final status,
1072
+ # taking into account any failures in the run or the post-run finish steps.
1073
+ if (
1074
+ self._state.run_result == RunResult.FAILED
1075
+ or self._state.finish_error is not None
1076
+ ):
1077
+ return EngineStatus.FAILED
1078
+ elif self._state.run_result == RunResult.SUCCEEDED:
1079
+ return EngineStatus.SUCCEEDED
1080
+ else:
1081
+ return EngineStatus.STOPPED
1082
+ else:
1083
+ # Post-run finish steps have not yet completed,
1084
+ # and we may even still be executing commands.
1085
+ return (
1086
+ EngineStatus.STOP_REQUESTED
1087
+ if self._state.run_result == RunResult.STOPPED
1088
+ else EngineStatus.FINISHING
1089
+ )
1090
+
1091
+ elif self._state.queue_status == QueueStatus.RUNNING:
1092
+ return EngineStatus.RUNNING
1093
+
1094
+ elif self._state.queue_status == QueueStatus.PAUSED:
1095
+ if self._state.is_door_blocking:
1096
+ return EngineStatus.BLOCKED_BY_OPEN_DOOR
1097
+ else:
1098
+ return EngineStatus.PAUSED
1099
+
1100
+ elif self._state.queue_status == QueueStatus.AWAITING_RECOVERY_PAUSED:
1101
+ if self._state.is_door_blocking:
1102
+ return EngineStatus.AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR
1103
+ else:
1104
+ return EngineStatus.AWAITING_RECOVERY_PAUSED
1105
+
1106
+ elif self._state.queue_status == QueueStatus.AWAITING_RECOVERY:
1107
+ return EngineStatus.AWAITING_RECOVERY
1108
+
1109
+ # todo(mm, 2024-03-19): Does this intentionally return idle if QueueStatus is
1110
+ # SETUP and we're currently a setup command?
1111
+ return EngineStatus.IDLE
1112
+
1113
+ def get_latest_protocol_command_hash(self) -> Optional[str]:
1114
+ """Get the command hash of the last queued command, if any."""
1115
+ return self._state.latest_protocol_command_hash
1116
+
1117
+ def get_error_recovery_policy(self) -> ErrorRecoveryPolicy:
1118
+ """Return the run's current error recovery policy (see `ErrorRecoveryPolicy`).
1119
+
1120
+ This error recovery policy is not ever evaluated by
1121
+ `CommandStore`/`CommandView`. It's stored here for convenience, but evaluated by
1122
+ higher-level code.
1123
+ """
1124
+ return self._state.error_recovery_policy
1125
+
1126
+ def get_state_update_for_false_positive(self) -> update_types.StateUpdate:
1127
+ """Return the state update for if the current recovery target was a false positive.
1128
+
1129
+ If we're currently in error recovery mode, and you have decided that the
1130
+ underlying command error was a false positive, this returns a state update
1131
+ that will undo the error's effects on engine state.
1132
+ See `ProtocolEngine.resume_from_recovery(reconcile_false_positive=True)`.
1133
+ """
1134
+ if self._state.recovery_target is None:
1135
+ return update_types.StateUpdate() # Empty/no-op.
1136
+ else:
1137
+ return self._state.recovery_target.state_update_if_false_positive
1138
+
1139
+ def _may_run_with_door_open(
1140
+ self, *, fixit_command: Command | CommandCreate
1141
+ ) -> bool:
1142
+ """Return whether the given fixit command is exempt from the usual open-door auto pause.
1143
+
1144
+ This is required for certain error recovery flows, where we want the robot to
1145
+ do stuff while the door is open.
1146
+ """
1147
+ # CommandIntent.PROTOCOL and CommandIntent.SETUP have their own rules for whether
1148
+ # they run while the door is open. Passing one of those commands to this function
1149
+ # is probably a mistake in the caller's logic.
1150
+ assert fixit_command.intent == CommandIntent.FIXIT
1151
+
1152
+ # These type annotations are to make sure the string constants stay in sync and aren't typo'd.
1153
+ allowed_command_types: Tuple[
1154
+ UnsafeUngripLabwareCommandType,
1155
+ UnsafeFlexStackerCloseLatchCommandType,
1156
+ UnsafeFlexStackerOpenLatchCommandType,
1157
+ ] = (
1158
+ "unsafe/ungripLabware",
1159
+ "unsafe/flexStacker/closeLatch",
1160
+ "unsafe/flexStacker/openLatch",
1161
+ )
1162
+ # todo(mm, 2024-10-04): Instead of allowlisting command types, maybe we should
1163
+ # add a `mayRunWithDoorOpen: bool` field to command requests.
1164
+ return fixit_command.commandType in allowed_command_types