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,948 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ from typing import (
6
+ Any,
7
+ Awaitable,
8
+ Callable,
9
+ Dict,
10
+ List,
11
+ Literal,
12
+ Optional,
13
+ Mapping,
14
+ cast,
15
+ )
16
+
17
+ from opentrons.drivers.flex_stacker.types import (
18
+ AxisParams,
19
+ Direction,
20
+ LEDColor,
21
+ LEDPattern,
22
+ MoveParams,
23
+ MoveResult,
24
+ StackerAxis,
25
+ StallGuardParams,
26
+ TOFDetection,
27
+ TOFMeasurementResult,
28
+ TOFSensor,
29
+ HardwareRevision,
30
+ TOFSensorMode,
31
+ TOFSensorState,
32
+ TOFSensorStatus,
33
+ )
34
+ from opentrons.drivers.rpi_drivers.types import USBPort
35
+ from opentrons.drivers.flex_stacker.driver import (
36
+ FlexStackerDriver,
37
+ )
38
+ from opentrons.drivers.flex_stacker.abstract import AbstractFlexStackerDriver
39
+ from opentrons.drivers.flex_stacker.simulator import SimulatingDriver
40
+ from opentrons.hardware_control.execution_manager import ExecutionManager
41
+ from opentrons.hardware_control.poller import Reader, Poller
42
+ from opentrons.hardware_control.modules import mod_abc, update
43
+ from opentrons.hardware_control.modules.types import (
44
+ FlexStackerStatus,
45
+ HopperDoorState,
46
+ LatchState,
47
+ ModuleDisconnectedCallback,
48
+ ModuleType,
49
+ PlatformState,
50
+ StackerAxisState,
51
+ UploadFunction,
52
+ LiveData,
53
+ FlexStackerData,
54
+ )
55
+ from opentrons.hardware_control.types import StatusBarState, StatusBarUpdateEvent
56
+ from opentrons.config import feature_flags as ff
57
+
58
+ from opentrons_shared_data.errors.exceptions import (
59
+ FlexStackerStallError,
60
+ FlexStackerShuttleMissingError,
61
+ FlexStackerShuttleLabwareError,
62
+ FlexStackerHopperLabwareError,
63
+ FlexStackerShuttleNotEmptyError,
64
+ )
65
+ from opentrons_shared_data.module import load_tof_baseline_data
66
+
67
+ log = logging.getLogger(__name__)
68
+
69
+ POLL_PERIOD = 2.0
70
+ SIMULATING_POLL_PERIOD = POLL_PERIOD / 20.0
71
+
72
+ DFU_PID = "df11"
73
+
74
+ # Maximum distance in mm the axis can travel.
75
+ MAX_TRAVEL = {
76
+ StackerAxis.X: 194.0,
77
+ StackerAxis.Z: 139.5,
78
+ StackerAxis.L: 22.0,
79
+ }
80
+
81
+ # Min/Max height in mm of labware stack to store/dispense
82
+ MIN_LABWARE_HEIGHT = 4.0
83
+ MAX_LABWARE_HEIGHT = 102.5
84
+
85
+ # The offset in mm to subtract from MAX_TRAVEL when moving an axis before we home.
86
+ # This lets us use `move_axis` to move fast, leaving the axis OFFSET mm
87
+ # from the limit switch. Then we can use `home_axis` to move the axis the rest
88
+ # of the way until we trigger the expected limit switch.
89
+ HOME_OFFSET_SM = 5.0
90
+ HOME_OFFSET_MD = 10.0
91
+
92
+ # The labware platform will contact the labware this mm before the platform
93
+ # touches the +Z endstop.
94
+ PLATFORM_OFFSET = 2.25
95
+
96
+ # Should put the bottom of the plate above this mm above the latch when dispensing.
97
+ # Should put the bottom of the plate this mm below the latch when storing.
98
+ LATCH_CLEARANCE = 2.5
99
+
100
+ # TOF Baseline Configs
101
+ # These are generated manually with the help of the tof_analysis.py tool
102
+ # Which can be found in `hardware_testing/tools/tof-analysis/README.md` where
103
+ # it goes over using use the tool.
104
+ TOF_DETECTION_CONFIG = {
105
+ TOFSensor.X: {
106
+ Direction.EXTEND: TOFDetection(
107
+ TOFSensor.X,
108
+ zones=[5, 6, 7],
109
+ bins=list(range(30, 40)),
110
+ threshold=1000,
111
+ ),
112
+ Direction.RETRACT: TOFDetection(
113
+ TOFSensor.X,
114
+ zones=[5, 6, 7],
115
+ bins=list(range(17, 30)),
116
+ threshold=1000,
117
+ ),
118
+ },
119
+ TOFSensor.Z: {
120
+ Direction.EXTEND: TOFDetection(
121
+ TOFSensor.Z,
122
+ zones=[1, 2, 3],
123
+ bins=list(range(15, 63)),
124
+ threshold=1000,
125
+ ),
126
+ Direction.RETRACT: TOFDetection(
127
+ TOFSensor.Z,
128
+ zones=[1, 2, 3],
129
+ bins=list(range(15, 63)),
130
+ threshold=1000,
131
+ ),
132
+ },
133
+ }
134
+
135
+
136
+ # Stallguard defaults
137
+ STALLGUARD_CONFIG = {
138
+ StackerAxis.X: StallGuardParams(StackerAxis.X, True, 0),
139
+ StackerAxis.Z: StallGuardParams(StackerAxis.Z, True, 2),
140
+ }
141
+
142
+ # Motion Parameter defaults
143
+ STACKER_MOTION_CONFIG = {
144
+ StackerAxis.X: {
145
+ "home": AxisParams(
146
+ run_current=1.5, # Amps RMS
147
+ hold_current=0.75,
148
+ move_params=MoveParams(
149
+ max_speed=10.0, # mm/s
150
+ acceleration=100.0, # mm/s^2
151
+ max_speed_discont=40.0, # mm/s
152
+ ),
153
+ ),
154
+ "move": AxisParams(
155
+ run_current=1.2,
156
+ hold_current=0.75,
157
+ move_params=MoveParams(
158
+ max_speed=200.0,
159
+ acceleration=1500.0,
160
+ max_speed_discont=40.0,
161
+ ),
162
+ ),
163
+ },
164
+ StackerAxis.Z: {
165
+ "home": AxisParams(
166
+ run_current=1.5,
167
+ hold_current=1.5,
168
+ move_params=MoveParams(
169
+ max_speed=10.0,
170
+ acceleration=100.0,
171
+ max_speed_discont=25.0,
172
+ ),
173
+ ),
174
+ "move": AxisParams(
175
+ run_current=1.5,
176
+ hold_current=1.5,
177
+ move_params=MoveParams(
178
+ max_speed=150.0,
179
+ acceleration=500.0,
180
+ max_speed_discont=25.0,
181
+ ),
182
+ ),
183
+ },
184
+ StackerAxis.L: {
185
+ "home": AxisParams(
186
+ run_current=1.2,
187
+ hold_current=0.5,
188
+ move_params=MoveParams(
189
+ max_speed=100.0,
190
+ acceleration=800.0,
191
+ max_speed_discont=40.0,
192
+ ),
193
+ ),
194
+ "move": AxisParams(
195
+ run_current=1.2,
196
+ hold_current=0.5,
197
+ move_params=MoveParams(
198
+ max_speed=100.0,
199
+ acceleration=800.0,
200
+ max_speed_discont=40.0,
201
+ ),
202
+ ),
203
+ },
204
+ }
205
+
206
+
207
+ class FlexStacker(mod_abc.AbstractModule):
208
+ """Hardware control interface for an attached Flex-Stacker module."""
209
+
210
+ MODULE_TYPE = ModuleType.FLEX_STACKER
211
+
212
+ @classmethod
213
+ async def build(
214
+ cls,
215
+ port: str,
216
+ usb_port: USBPort,
217
+ hw_control_loop: asyncio.AbstractEventLoop,
218
+ execution_manager: Optional[ExecutionManager] = None,
219
+ poll_interval_seconds: Optional[float] = None,
220
+ simulating: bool = False,
221
+ sim_model: Optional[str] = None,
222
+ sim_serial_number: Optional[str] = None,
223
+ disconnected_callback: ModuleDisconnectedCallback = None,
224
+ ) -> "FlexStacker":
225
+ """
226
+ Build a FlexStacker
227
+
228
+ Args:
229
+ port: The port to connect to
230
+ usb_port: USB Port
231
+ execution_manager: Execution manager.
232
+ hw_control_loop: The event loop running in the hardware control thread.
233
+ poll_interval_seconds: Poll interval override.
234
+ simulating: whether to build a simulating driver
235
+ loop: Loop
236
+ sim_model: The model name used by simulator
237
+ disconnected_callback: Callback to inform the module controller that the device was disconnected
238
+
239
+ Returns:
240
+ FlexStacker instance
241
+ """
242
+ driver: AbstractFlexStackerDriver
243
+ if not simulating:
244
+ driver = await FlexStackerDriver.create(port=port, loop=hw_control_loop)
245
+ poll_interval_seconds = poll_interval_seconds or POLL_PERIOD
246
+ else:
247
+ driver = SimulatingDriver(serial_number=sim_serial_number)
248
+ poll_interval_seconds = poll_interval_seconds or SIMULATING_POLL_PERIOD
249
+
250
+ reader = FlexStackerReader(driver=driver)
251
+ poller = Poller(reader=reader, interval=poll_interval_seconds)
252
+ module = cls(
253
+ port=port,
254
+ usb_port=usb_port,
255
+ driver=driver,
256
+ reader=reader,
257
+ poller=poller,
258
+ device_info=(await driver.get_device_info()).to_dict(),
259
+ hw_control_loop=hw_control_loop,
260
+ execution_manager=execution_manager,
261
+ disconnected_callback=disconnected_callback,
262
+ )
263
+
264
+ # Set initialized callback
265
+ reader.set_initialized_callback(module._initialized_callback)
266
+
267
+ # Enable stallguard
268
+ for axis, config in STALLGUARD_CONFIG.items():
269
+ await driver.set_stallguard_threshold(
270
+ axis, config.enabled, config.threshold
271
+ )
272
+
273
+ try:
274
+ await poller.start()
275
+ except Exception:
276
+ log.exception(f"First read of Flex-Stacker on port {port} failed")
277
+
278
+ return module
279
+
280
+ def __init__(
281
+ self,
282
+ port: str,
283
+ usb_port: USBPort,
284
+ driver: AbstractFlexStackerDriver,
285
+ reader: FlexStackerReader,
286
+ poller: Poller,
287
+ device_info: Mapping[str, str],
288
+ hw_control_loop: asyncio.AbstractEventLoop,
289
+ execution_manager: Optional[ExecutionManager] = None,
290
+ disconnected_callback: ModuleDisconnectedCallback = None,
291
+ ):
292
+ super().__init__(
293
+ port=port,
294
+ usb_port=usb_port,
295
+ hw_control_loop=hw_control_loop,
296
+ execution_manager=execution_manager,
297
+ disconnected_callback=disconnected_callback,
298
+ )
299
+ self._device_info = device_info
300
+ self._driver = driver
301
+ self._reader = reader
302
+ self._poller = poller
303
+ self._stall_detected = False
304
+ self._stacker_status = FlexStackerStatus.IDLE
305
+ self._last_status_bar_event: Optional[StatusBarUpdateEvent] = None
306
+ self._should_identify = False
307
+
308
+ async def _initialized_callback(self) -> None:
309
+ """Called by the reader once the module is initialized."""
310
+ if self._last_status_bar_event:
311
+ await self._handle_status_bar_event(self._last_status_bar_event)
312
+
313
+ async def cleanup(self) -> None:
314
+ """Stop the poller task"""
315
+ await self._poller.stop()
316
+ await self._driver.disconnect()
317
+
318
+ @classmethod
319
+ def name(cls) -> str:
320
+ """Used for picking up serial port symlinks"""
321
+ return "flexstacker"
322
+
323
+ def firmware_prefix(self) -> str:
324
+ """The prefix used for looking up firmware"""
325
+ return "flex-stacker"
326
+
327
+ @staticmethod
328
+ def _model_from_revision(revision: Optional[str]) -> str:
329
+ """Defines the revision -> model mapping"""
330
+ return "flexStackerModuleV1"
331
+
332
+ def model(self) -> str:
333
+ return self._model_from_revision(self._device_info.get("model"))
334
+
335
+ @property
336
+ def latch_state(self) -> LatchState:
337
+ """The state of the latch."""
338
+ return LatchState.from_state(self.limit_switch_status[StackerAxis.L])
339
+
340
+ @property
341
+ def platform_state(self) -> PlatformState:
342
+ """The state of the platform."""
343
+ return self._reader.platform_state
344
+
345
+ @property
346
+ def initialized(self) -> bool:
347
+ """The stacker is ready..."""
348
+ return self._reader.initialized
349
+
350
+ @property
351
+ def hopper_door_state(self) -> HopperDoorState:
352
+ """The status of the hopper door."""
353
+ return HopperDoorState.from_state(self._reader.hopper_door_closed)
354
+
355
+ @property
356
+ def limit_switch_status(self) -> Dict[StackerAxis, StackerAxisState]:
357
+ """The status of the Limit switches."""
358
+ return self._reader.limit_switch_status
359
+
360
+ @property
361
+ def install_detected(self) -> bool:
362
+ """Whether the stacker is installed on Flex."""
363
+ return self._reader.installation_detected
364
+
365
+ @property
366
+ def device_info(self) -> Mapping[str, str]:
367
+ return self._device_info
368
+
369
+ @property
370
+ def status(self) -> FlexStackerStatus:
371
+ """Module status or error state details."""
372
+ return self._stacker_status
373
+
374
+ @property
375
+ def is_simulated(self) -> bool:
376
+ return isinstance(self._driver, SimulatingDriver)
377
+
378
+ @property
379
+ def live_data(self) -> LiveData:
380
+ data: FlexStackerData = {
381
+ "latchState": self.latch_state.value,
382
+ "platformState": self.platform_state.value,
383
+ "hopperDoorState": self.hopper_door_state.value,
384
+ "installDetected": self.install_detected,
385
+ "errorDetails": self._reader.error,
386
+ }
387
+ return {"status": self.status.value, "data": data}
388
+
389
+ @property
390
+ def should_identify(self) -> bool:
391
+ return self._should_identify
392
+
393
+ async def prep_for_update(self) -> str:
394
+ await self._poller.stop()
395
+ await self._driver.stop_motors()
396
+ await self._driver.enter_programming_mode()
397
+ # flex stacker has three unique "devices" over DFU
398
+ dfu_info = await update.find_dfu_device(pid=DFU_PID, expected_device_count=3)
399
+ return dfu_info
400
+
401
+ def bootloader(self) -> UploadFunction:
402
+ return update.upload_via_dfu
403
+
404
+ async def deactivate(self, must_be_running: bool = True) -> None:
405
+ await self._driver.stop_motors()
406
+
407
+ async def set_led_state(
408
+ self,
409
+ power: float,
410
+ color: Optional[LEDColor] = None,
411
+ pattern: Optional[LEDPattern] = None,
412
+ duration: Optional[int] = None,
413
+ reps: Optional[int] = None,
414
+ ) -> None:
415
+ """Sets the statusbar state."""
416
+ return await self._driver.set_led(
417
+ power, color=color, pattern=pattern, duration=duration, reps=reps
418
+ )
419
+
420
+ async def move_axis(
421
+ self,
422
+ axis: StackerAxis,
423
+ direction: Direction,
424
+ distance: float,
425
+ speed: Optional[float] = None,
426
+ acceleration: Optional[float] = None,
427
+ current: Optional[float] = None,
428
+ ) -> bool:
429
+ """Move the axis in a direction by the given distance in mm."""
430
+ default = STACKER_MOTION_CONFIG[axis]["move"]
431
+ old_run_current = self._reader.motion_params[axis].run_current
432
+ new_run_current = current if current is not None else default.run_current
433
+ if new_run_current != old_run_current:
434
+ await self._driver.set_run_current(axis, new_run_current)
435
+ self._reader.motion_params[axis].run_current = new_run_current
436
+
437
+ old_hold_current = self._reader.motion_params[axis].hold_current
438
+ new_hold_current = default.hold_current
439
+ if new_hold_current != old_hold_current:
440
+ await self._driver.set_ihold_current(axis, new_hold_current)
441
+ self._reader.motion_params[axis].hold_current = new_hold_current
442
+
443
+ motion_params = default.move_params.update(
444
+ max_speed=speed, acceleration=acceleration
445
+ )
446
+ distance = direction.distance(distance)
447
+ res = await self._driver.move_in_mm(axis, distance, params=motion_params)
448
+ if res == MoveResult.STALL_ERROR:
449
+ self._stall_detected = True
450
+ raise FlexStackerStallError(self.device_info["serial"], axis)
451
+ return res == MoveResult.NO_ERROR
452
+
453
+ async def home_axis(
454
+ self,
455
+ axis: StackerAxis,
456
+ direction: Direction,
457
+ speed: Optional[float] = None,
458
+ acceleration: Optional[float] = None,
459
+ current: Optional[float] = None,
460
+ ) -> bool:
461
+ default = STACKER_MOTION_CONFIG[axis]["home"]
462
+ old_run_current = self._reader.motion_params[axis].run_current
463
+ new_run_current = current if current is not None else default.run_current
464
+ if new_run_current != old_run_current:
465
+ await self._driver.set_run_current(axis, new_run_current)
466
+ self._reader.motion_params[axis].run_current = new_run_current
467
+
468
+ old_hold_current = self._reader.motion_params[axis].hold_current
469
+ new_hold_current = default.hold_current
470
+ if new_hold_current != old_hold_current:
471
+ await self._driver.set_ihold_current(axis, new_hold_current)
472
+ self._reader.motion_params[axis].hold_current = new_hold_current
473
+
474
+ motion_params = default.move_params.update(
475
+ max_speed=speed, acceleration=acceleration
476
+ )
477
+ success = await self._driver.move_to_limit_switch(
478
+ axis=axis, direction=direction, params=motion_params
479
+ )
480
+ await self._reader.get_limit_switch_status()
481
+ if success == MoveResult.STALL_ERROR:
482
+ self._stall_detected = True
483
+ raise FlexStackerStallError(self.device_info["serial"], axis)
484
+ return success == MoveResult.NO_ERROR
485
+
486
+ async def close_latch(
487
+ self,
488
+ velocity: Optional[float] = None,
489
+ acceleration: Optional[float] = None,
490
+ ) -> bool:
491
+ """Close the latch, dropping any labware its holding."""
492
+ success = await self.home_axis(
493
+ StackerAxis.L,
494
+ Direction.RETRACT,
495
+ speed=velocity,
496
+ acceleration=acceleration,
497
+ )
498
+ # Check that the latch is closed.
499
+ await self._reader.get_limit_switch_status()
500
+ return (
501
+ success
502
+ and self.limit_switch_status[StackerAxis.L] == StackerAxisState.EXTENDED
503
+ )
504
+
505
+ async def open_latch(
506
+ self,
507
+ velocity: Optional[float] = None,
508
+ acceleration: Optional[float] = None,
509
+ ) -> bool:
510
+ """Open the latch."""
511
+ # The latch only has one limit switch, so we have to travel a fixed distance
512
+ # to open the latch.
513
+ success = await self.move_axis(
514
+ StackerAxis.L,
515
+ Direction.EXTEND,
516
+ distance=MAX_TRAVEL[StackerAxis.L],
517
+ speed=velocity,
518
+ acceleration=acceleration,
519
+ )
520
+ # Check that the latch is opened.
521
+ await self._reader.get_limit_switch_status()
522
+ return (
523
+ success
524
+ and self.limit_switch_status[StackerAxis.L] == StackerAxisState.RETRACTED
525
+ )
526
+
527
+ async def dispense_labware(
528
+ self,
529
+ labware_height: float,
530
+ enforce_hopper_lw_sensing: bool = True,
531
+ enforce_shuttle_lw_sensing: bool = True,
532
+ ) -> None:
533
+ """Dispenses the next labware in the stacker."""
534
+ self.verify_labware_height(labware_height)
535
+ await self._prepare_for_action()
536
+
537
+ if enforce_hopper_lw_sensing:
538
+ # TODO: re-enable this function after TOF calibration is implemented.
539
+ # Until then, we should also check the TOF X sensor before raising the error
540
+ # await self.verify_hopper_labware_presence(Direction.EXTEND, True)
541
+ hopper_empty = not await self.labware_detected(
542
+ StackerAxis.Z, Direction.EXTEND
543
+ )
544
+
545
+ # Move platform along the X and make sure we DONT detect labware
546
+ await self._move_and_home_axis(StackerAxis.X, Direction.RETRACT, HOME_OFFSET_MD)
547
+ if enforce_shuttle_lw_sensing:
548
+ await self.verify_shuttle_labware_presence(Direction.RETRACT, False)
549
+
550
+ # Move platform along the Z axis
551
+ await self._move_and_home_axis(StackerAxis.Z, Direction.EXTEND, HOME_OFFSET_SM)
552
+
553
+ # Transfer
554
+ await self.open_latch()
555
+ latch_clear_distance = labware_height + PLATFORM_OFFSET - LATCH_CLEARANCE
556
+ await self.move_axis(StackerAxis.Z, Direction.RETRACT, latch_clear_distance)
557
+ await self.close_latch()
558
+
559
+ # Move Z down the rest of the way
560
+ z_distance = MAX_TRAVEL[StackerAxis.Z] - latch_clear_distance - HOME_OFFSET_SM
561
+ await self.move_axis(StackerAxis.Z, Direction.RETRACT, z_distance)
562
+ await self.home_axis(StackerAxis.Z, Direction.RETRACT)
563
+
564
+ if enforce_shuttle_lw_sensing:
565
+ try:
566
+ await self.verify_shuttle_labware_presence(Direction.RETRACT, True)
567
+ except FlexStackerShuttleLabwareError:
568
+ # No labware detected on the shuttle, so we need to check what the Z TOF
569
+ # sensor says about the hopper
570
+ if hopper_empty:
571
+ # homing here so we don't have to modify the error recovery flow
572
+ await self._move_and_home_axis(
573
+ StackerAxis.X, Direction.EXTEND, HOME_OFFSET_MD
574
+ )
575
+ raise FlexStackerHopperLabwareError(
576
+ self.device_info["serial"],
577
+ labware_expected=True,
578
+ ) from None
579
+ raise
580
+
581
+ await self._move_and_home_axis(StackerAxis.X, Direction.EXTEND, HOME_OFFSET_MD)
582
+
583
+ async def store_labware(
584
+ self,
585
+ labware_height: float,
586
+ enforce_shuttle_lw_sensing: bool = True,
587
+ ) -> None:
588
+ """Stores a labware in the stacker."""
589
+ self.verify_labware_height(labware_height)
590
+ await self._prepare_for_action()
591
+
592
+ # Move the X and check that labware is detected
593
+ await self._move_and_home_axis(StackerAxis.X, Direction.RETRACT, HOME_OFFSET_MD)
594
+ if enforce_shuttle_lw_sensing:
595
+ await self.verify_shuttle_labware_presence(Direction.RETRACT, True)
596
+
597
+ # Move the Z so the labware sits right under the labware already stored
598
+ latch_clear_distance = labware_height + PLATFORM_OFFSET - LATCH_CLEARANCE
599
+ distance = MAX_TRAVEL[StackerAxis.Z] - latch_clear_distance
600
+ await self.move_axis(StackerAxis.Z, Direction.EXTEND, distance)
601
+
602
+ await self.open_latch()
603
+ # Move the labware the rest of the way at half move speed to increase torque.
604
+ remaining_z = latch_clear_distance - HOME_OFFSET_SM
605
+ speed_z = STACKER_MOTION_CONFIG[StackerAxis.Z]["move"].move_params.max_speed / 2
606
+ await self.move_axis(StackerAxis.Z, Direction.EXTEND, remaining_z, speed_z)
607
+ await self.home_axis(StackerAxis.Z, Direction.EXTEND, speed_z)
608
+ await self.close_latch()
609
+
610
+ # Move the Z down and check that labware is not detected.
611
+ await self._move_and_home_axis(StackerAxis.Z, Direction.RETRACT, HOME_OFFSET_MD)
612
+ if enforce_shuttle_lw_sensing:
613
+ await self.verify_shuttle_labware_presence(Direction.RETRACT, False)
614
+
615
+ # Move the X to the gripper position
616
+ await self._move_and_home_axis(StackerAxis.X, Direction.EXTEND, HOME_OFFSET_MD)
617
+
618
+ async def _move_and_home_axis(
619
+ self,
620
+ axis: StackerAxis,
621
+ direction: Direction,
622
+ offset: float = 0,
623
+ speed: Optional[float] = None,
624
+ ) -> bool:
625
+ """Move the axis in a direction by the given offset in mm and home it.
626
+
627
+ Warning: It is assumed that the axis is already in a known state
628
+ before this function gets called. Do not use this function if the axis
629
+ has not been homed/has recently stalled."""
630
+ distance = MAX_TRAVEL[axis] - offset
631
+ await self.move_axis(axis, direction, distance, speed)
632
+ return await self.home_axis(axis, direction, speed)
633
+
634
+ async def _prepare_for_action(self) -> None:
635
+ """Helper to prepare axis for dispensing or storing labware."""
636
+ # TODO: check if we need to home first
637
+ await self.home_axis(StackerAxis.X, Direction.EXTEND)
638
+ await self.home_axis(StackerAxis.Z, Direction.RETRACT)
639
+ await self.close_latch()
640
+ await self.verify_shuttle_location(PlatformState.EXTENDED)
641
+
642
+ async def home_all(self, ignore_latch: bool = False) -> None:
643
+ """Home all axes based on current state, assuming normal operation.
644
+
645
+ If ignore_latch is True, we will not attempt to close the latch. This
646
+ is useful when we want the shuttle to be out of the way for error
647
+ recovery (e.g. when the latch is stuck open).
648
+ """
649
+ await self._reader.get_installation_detected()
650
+ await self._reader.get_limit_switch_status()
651
+ await self._reader.get_platform_sensor_state()
652
+
653
+ # Z axis is unknown, lets move it up in case it is holding a labware
654
+ if not ignore_latch:
655
+ if self.limit_switch_status[StackerAxis.Z] == StackerAxisState.UNKNOWN:
656
+ if self.latch_state == LatchState.OPENED:
657
+ # let's make sure the latch is opened all the way before homging the Z
658
+ await self.open_latch()
659
+ # self.latch_state is OPENED, so we need to home Z in the EXTEND direction
660
+ await self.home_axis(StackerAxis.Z, Direction.EXTEND)
661
+ await self.close_latch()
662
+
663
+ if (
664
+ # if the platform is on the z or if x has not been homed
665
+ self.platform_state == PlatformState.UNKNOWN
666
+ or self.limit_switch_status[StackerAxis.X] == StackerAxisState.UNKNOWN
667
+ ):
668
+ # if the z is not retracted, we need to make sure the x is retracted
669
+ # so we can retract the z properly later
670
+ if self.limit_switch_status[StackerAxis.Z] != StackerAxisState.RETRACTED:
671
+ await self.home_axis(StackerAxis.X, Direction.RETRACT)
672
+ else:
673
+ await self.home_axis(StackerAxis.X, Direction.EXTEND)
674
+
675
+ # Finally, retract Z and extend X if they are not already
676
+ await self.home_axis(StackerAxis.Z, Direction.RETRACT)
677
+ await self.home_axis(StackerAxis.X, Direction.EXTEND)
678
+
679
+ async def labware_detected(
680
+ self,
681
+ axis: StackerAxis,
682
+ direction: Direction,
683
+ histogram: Optional[TOFMeasurementResult] = None,
684
+ baseline: Optional[Dict[int, List[float]]] = None,
685
+ ) -> bool:
686
+ """Detect labware on the TOF sensor using the `baseline` method
687
+
688
+ NOTE: This method is still under development and is inconsistent when detecting
689
+ labware on the X axis in the Extended position. We can consistently detect
690
+ labware on the Z, but we need to do more data collection and testing
691
+ to validate this method.
692
+ """
693
+ dir_str = cast(Literal["extend", "retract"], str(direction))
694
+ sensor = TOFSensor.X if axis == StackerAxis.X else TOFSensor.Z
695
+ baseline = (
696
+ baseline or load_tof_baseline_data(self.model())[sensor.value][dir_str]
697
+ )
698
+ config = TOF_DETECTION_CONFIG[sensor][direction]
699
+
700
+ # Take a histogram reading and determine if labware was detected
701
+ histogram = histogram or await self._driver.get_tof_histogram(sensor)
702
+ for zone in config.zones:
703
+ raw_data = histogram.bins[zone]
704
+ baseline_data = baseline[zone]
705
+ for bin in config.bins:
706
+ # We need to ignore raw photon count below N photons as
707
+ # it becomes inconsistent to detect labware given false positives.
708
+ if raw_data[bin] < config.threshold:
709
+ continue
710
+ delta = raw_data[bin] - baseline_data[bin]
711
+ if delta > 0:
712
+ return True
713
+ return False
714
+
715
+ async def verify_shuttle_location(self, expected: PlatformState) -> None:
716
+ """Verify the shuttle is present and in the expected location."""
717
+ await self._reader.get_platform_sensor_state()
718
+ # Validate the platform state matches, ignore EXTENDED checks on EVT
719
+ if self.platform_state != expected:
720
+ if (
721
+ self.device_info["model"] == HardwareRevision.EVT.value
722
+ and expected == PlatformState.EXTENDED
723
+ ):
724
+ return
725
+ else:
726
+ raise FlexStackerShuttleMissingError(
727
+ self.device_info["serial"], expected, self.platform_state
728
+ )
729
+
730
+ async def verify_shuttle_labware_presence(
731
+ self, direction: Direction, labware_expected: bool
732
+ ) -> None:
733
+ """Check whether or not a labware is detected on the shuttle."""
734
+ if ff.flex_stacker_tof_sensors_disabled():
735
+ return
736
+ result = await self.labware_detected(StackerAxis.X, direction)
737
+ if labware_expected != result:
738
+ if labware_expected:
739
+ raise FlexStackerShuttleLabwareError(
740
+ self.device_info["serial"],
741
+ shuttle_state=self.platform_state,
742
+ labware_expected=labware_expected,
743
+ )
744
+ raise FlexStackerShuttleNotEmptyError(
745
+ self.device_info["serial"],
746
+ shuttle_state=self.platform_state,
747
+ labware_expected=labware_expected,
748
+ )
749
+
750
+ async def verify_hopper_labware_presence(
751
+ self, direction: Direction, labware_expected: bool
752
+ ) -> None:
753
+ """Check whether or not a labware is detected inside the hopper."""
754
+ if ff.flex_stacker_tof_sensors_disabled():
755
+ return
756
+ result = await self.labware_detected(StackerAxis.Z, direction)
757
+ if labware_expected != result:
758
+ raise FlexStackerHopperLabwareError(
759
+ self.device_info["serial"],
760
+ labware_expected=labware_expected,
761
+ )
762
+
763
+ def verify_labware_height(self, labware_height: float) -> None:
764
+ """Check that the labware height is within valid range."""
765
+ if labware_height < MIN_LABWARE_HEIGHT or labware_height > MAX_LABWARE_HEIGHT:
766
+ raise ValueError(
767
+ f"Labware height must be between {MIN_LABWARE_HEIGHT}-{MAX_LABWARE_HEIGHT}mm."
768
+ "Received {labware_height}mm."
769
+ )
770
+
771
+ def event_listener(self, event: Any) -> None:
772
+ if isinstance(event, StatusBarUpdateEvent):
773
+ self._last_status_bar_event = event
774
+ asyncio.run_coroutine_threadsafe(
775
+ self._handle_status_bar_event(event), self._loop
776
+ )
777
+
778
+ async def _handle_status_bar_event(self, event: StatusBarUpdateEvent) -> None:
779
+ if event.enabled and self.initialized:
780
+ match event.state:
781
+ case StatusBarState.RUNNING:
782
+ await self.set_led_state(0.5, LEDColor.GREEN, LEDPattern.STATIC)
783
+ case StatusBarState.PAUSED:
784
+ if (
785
+ self.hopper_door_state == HopperDoorState.OPENED
786
+ or self.should_identify
787
+ ):
788
+ await self._stacker_bar_pause()
789
+ else:
790
+ await self._stacker_bar_idle()
791
+ case StatusBarState.IDLE:
792
+ if self.hopper_door_state == HopperDoorState.OPENED:
793
+ await self._stacker_bar_pause()
794
+ else:
795
+ await self._stacker_bar_idle()
796
+ case StatusBarState.HARDWARE_ERROR:
797
+ if self.should_identify:
798
+ await self.set_led_state(
799
+ 0.5, LEDColor.RED, LEDPattern.FLASH, duration=300
800
+ )
801
+ else:
802
+ await self._stacker_bar_idle()
803
+ case StatusBarState.SOFTWARE_ERROR:
804
+ await self.set_led_state(0.5, LEDColor.YELLOW, LEDPattern.STATIC)
805
+ case StatusBarState.ERROR_RECOVERY:
806
+ if self.hopper_door_state == HopperDoorState.OPENED:
807
+ await self._stacker_bar_pause()
808
+ elif self.should_identify:
809
+ await self.set_led_state(
810
+ 0.5, LEDColor.YELLOW, LEDPattern.PULSE, duration=2000
811
+ )
812
+ else:
813
+ await self._stacker_bar_idle()
814
+ case StatusBarState.RUN_COMPLETED:
815
+ await self.set_led_state(0.5, LEDColor.GREEN, LEDPattern.PULSE)
816
+ case StatusBarState.UPDATING:
817
+ await self.set_led_state(0.5, LEDColor.WHITE, LEDPattern.PULSE)
818
+ case _:
819
+ await self._stacker_bar_idle()
820
+
821
+ async def _stacker_bar_pause(self) -> None:
822
+ await self.set_led_state(0.5, LEDColor.BLUE, LEDPattern.PULSE, duration=2000)
823
+
824
+ async def _stacker_bar_idle(self) -> None:
825
+ await self.set_led_state(0.5, LEDColor.WHITE, LEDPattern.STATIC)
826
+
827
+ async def identify(self, start: bool, color_name: Optional[str] = None) -> None:
828
+ """Identify the module."""
829
+ reps = -1 if start else 0
830
+ color = LEDColor.from_name(color_name or LEDColor.BLUE.name)
831
+ await self.set_led_state(0.5, color, LEDPattern.PULSE, reps=reps)
832
+ if not start and self._last_status_bar_event:
833
+ await self._handle_status_bar_event(self._last_status_bar_event)
834
+
835
+ def set_stacker_identify(self, state: bool) -> None:
836
+ self._should_identify = state
837
+
838
+ def cleanup_persistent(self) -> None:
839
+ """Reset persistent data on the module that should not exist outside of a run."""
840
+ self.set_stacker_identify(False)
841
+
842
+
843
+ class FlexStackerReader(Reader):
844
+ error: Optional[str]
845
+
846
+ def __init__(self, driver: AbstractFlexStackerDriver) -> None:
847
+ self.error: Optional[str] = None
848
+ self._driver = driver
849
+ self.limit_switch_status = {
850
+ axis: StackerAxisState.UNKNOWN for axis in StackerAxis
851
+ }
852
+ self.tof_sensor_status: Dict[TOFSensor, TOFSensorStatus] = {
853
+ s: TOFSensorStatus(
854
+ s, TOFSensorState.INITIALIZING, TOFSensorMode.UNKNOWN, False
855
+ )
856
+ for s in TOFSensor
857
+ }
858
+ self.motion_params: Dict[StackerAxis, AxisParams] = {
859
+ axis: AxisParams(0, 0, MoveParams(0, 0, 0)) for axis in StackerAxis
860
+ }
861
+ self.platform_state = PlatformState.UNKNOWN
862
+ self.hopper_door_closed = False
863
+ self.initialized = False
864
+ self.installation_detected = False
865
+ self._refresh_state = False
866
+ self._initialized_callback: Optional[Callable[[], Awaitable[None]]] = None
867
+
868
+ def set_initialized_callback(self, callback: Callable[[], Awaitable[None]]) -> None:
869
+ """Sets the callback used when done initializing the module."""
870
+ self._initialized_callback = callback
871
+
872
+ async def read(self) -> None:
873
+ await self.get_door_closed()
874
+ await self.get_platform_sensor_state()
875
+ if not self.initialized or self._refresh_state:
876
+ initialized = True
877
+ await self.get_installation_detected()
878
+ await self.get_limit_switch_status()
879
+ await self.get_motion_parameters()
880
+ for sensor, status in self.tof_sensor_status.items():
881
+ if status.state == TOFSensorState.INITIALIZING:
882
+ status = await self._driver.get_tof_sensor_status(sensor)
883
+ self.tof_sensor_status[sensor] = status
884
+ initialized &= status.ok
885
+
886
+ self._refresh_state = False
887
+ # We are done initializing, sync the led state
888
+ if not self.initialized and initialized:
889
+ self.initialized = True
890
+ if self._initialized_callback:
891
+ await self._initialized_callback()
892
+
893
+ self._set_error(None)
894
+
895
+ async def get_limit_switch_status(self) -> None:
896
+ """Get the limit switch status."""
897
+ status = await self._driver.get_limit_switches_status()
898
+ self.limit_switch_status = {
899
+ axis: StackerAxisState.from_status(status, axis) for axis in StackerAxis
900
+ }
901
+
902
+ async def get_motion_parameters(self) -> None:
903
+ """Get the motion parameters used by the axis motors."""
904
+ for axis in StackerAxis:
905
+ self.motion_params[axis].move_params = await self._driver.get_motion_params(
906
+ axis
907
+ )
908
+
909
+ async def get_platform_sensor_state(self) -> None:
910
+ """Get the platform state."""
911
+ status = await self._driver.get_platform_status()
912
+ platform_state = PlatformState.from_status(status)
913
+ if self.initialized and platform_state == PlatformState.UNKNOWN:
914
+ # If the platform state is unknown but the X axis is known,
915
+ # the platform is missing.
916
+ await self.get_limit_switch_status()
917
+ if self.limit_switch_status[StackerAxis.X] != StackerAxisState.UNKNOWN:
918
+ platform_state = PlatformState.MISSING
919
+ self.platform_state = platform_state
920
+
921
+ async def get_door_closed(self) -> None:
922
+ """Check if the hopper door is closed."""
923
+ old_door_state = self.hopper_door_closed
924
+ self.hopper_door_closed = await self._driver.get_hopper_door_closed()
925
+ if old_door_state != self.hopper_door_closed and self._initialized_callback:
926
+ await self._initialized_callback()
927
+
928
+ async def get_installation_detected(self) -> None:
929
+ """Check if the stacker install detect is set."""
930
+ detected = await self._driver.get_installation_detected()
931
+ self.installation_detected = detected
932
+
933
+ def set_refresh_state(self) -> None:
934
+ """Tell the reader to refresh all states, even ones that arent polled."""
935
+ self._refresh_state = True
936
+
937
+ def on_error(self, exception: Exception) -> None:
938
+ self._driver.reset_serial_buffers()
939
+ self._set_error(exception)
940
+
941
+ def _set_error(self, exception: Optional[Exception]) -> None:
942
+ if exception is None:
943
+ self.error = None
944
+ else:
945
+ try:
946
+ self.error = str(exception.args[0])
947
+ except Exception:
948
+ self.error = repr(exception)