opentrons 8.6.0a1__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (600) hide show
  1. opentrons/__init__.py +150 -0
  2. opentrons/_version.py +34 -0
  3. opentrons/calibration_storage/__init__.py +54 -0
  4. opentrons/calibration_storage/deck_configuration.py +62 -0
  5. opentrons/calibration_storage/encoder_decoder.py +31 -0
  6. opentrons/calibration_storage/file_operators.py +142 -0
  7. opentrons/calibration_storage/helpers.py +103 -0
  8. opentrons/calibration_storage/ot2/__init__.py +34 -0
  9. opentrons/calibration_storage/ot2/deck_attitude.py +85 -0
  10. opentrons/calibration_storage/ot2/mark_bad_calibration.py +27 -0
  11. opentrons/calibration_storage/ot2/models/__init__.py +0 -0
  12. opentrons/calibration_storage/ot2/models/v1.py +149 -0
  13. opentrons/calibration_storage/ot2/pipette_offset.py +129 -0
  14. opentrons/calibration_storage/ot2/tip_length.py +281 -0
  15. opentrons/calibration_storage/ot3/__init__.py +31 -0
  16. opentrons/calibration_storage/ot3/deck_attitude.py +83 -0
  17. opentrons/calibration_storage/ot3/gripper_offset.py +156 -0
  18. opentrons/calibration_storage/ot3/models/__init__.py +0 -0
  19. opentrons/calibration_storage/ot3/models/v1.py +122 -0
  20. opentrons/calibration_storage/ot3/module_offset.py +138 -0
  21. opentrons/calibration_storage/ot3/pipette_offset.py +95 -0
  22. opentrons/calibration_storage/types.py +45 -0
  23. opentrons/cli/__init__.py +21 -0
  24. opentrons/cli/__main__.py +5 -0
  25. opentrons/cli/analyze.py +501 -0
  26. opentrons/config/__init__.py +631 -0
  27. opentrons/config/advanced_settings.py +871 -0
  28. opentrons/config/defaults_ot2.py +214 -0
  29. opentrons/config/defaults_ot3.py +499 -0
  30. opentrons/config/feature_flags.py +86 -0
  31. opentrons/config/gripper_config.py +55 -0
  32. opentrons/config/reset.py +203 -0
  33. opentrons/config/robot_configs.py +187 -0
  34. opentrons/config/types.py +183 -0
  35. opentrons/drivers/__init__.py +0 -0
  36. opentrons/drivers/absorbance_reader/__init__.py +11 -0
  37. opentrons/drivers/absorbance_reader/abstract.py +72 -0
  38. opentrons/drivers/absorbance_reader/async_byonoy.py +352 -0
  39. opentrons/drivers/absorbance_reader/driver.py +81 -0
  40. opentrons/drivers/absorbance_reader/hid_protocol.py +161 -0
  41. opentrons/drivers/absorbance_reader/simulator.py +84 -0
  42. opentrons/drivers/asyncio/__init__.py +0 -0
  43. opentrons/drivers/asyncio/communication/__init__.py +22 -0
  44. opentrons/drivers/asyncio/communication/async_serial.py +183 -0
  45. opentrons/drivers/asyncio/communication/errors.py +88 -0
  46. opentrons/drivers/asyncio/communication/serial_connection.py +552 -0
  47. opentrons/drivers/command_builder.py +102 -0
  48. opentrons/drivers/flex_stacker/__init__.py +13 -0
  49. opentrons/drivers/flex_stacker/abstract.py +214 -0
  50. opentrons/drivers/flex_stacker/driver.py +768 -0
  51. opentrons/drivers/flex_stacker/errors.py +68 -0
  52. opentrons/drivers/flex_stacker/simulator.py +309 -0
  53. opentrons/drivers/flex_stacker/types.py +367 -0
  54. opentrons/drivers/flex_stacker/utils.py +19 -0
  55. opentrons/drivers/heater_shaker/__init__.py +5 -0
  56. opentrons/drivers/heater_shaker/abstract.py +76 -0
  57. opentrons/drivers/heater_shaker/driver.py +204 -0
  58. opentrons/drivers/heater_shaker/simulator.py +94 -0
  59. opentrons/drivers/mag_deck/__init__.py +6 -0
  60. opentrons/drivers/mag_deck/abstract.py +44 -0
  61. opentrons/drivers/mag_deck/driver.py +208 -0
  62. opentrons/drivers/mag_deck/simulator.py +63 -0
  63. opentrons/drivers/rpi_drivers/__init__.py +33 -0
  64. opentrons/drivers/rpi_drivers/dev_types.py +94 -0
  65. opentrons/drivers/rpi_drivers/gpio.py +282 -0
  66. opentrons/drivers/rpi_drivers/gpio_simulator.py +127 -0
  67. opentrons/drivers/rpi_drivers/interfaces.py +15 -0
  68. opentrons/drivers/rpi_drivers/types.py +364 -0
  69. opentrons/drivers/rpi_drivers/usb.py +102 -0
  70. opentrons/drivers/rpi_drivers/usb_simulator.py +22 -0
  71. opentrons/drivers/serial_communication.py +151 -0
  72. opentrons/drivers/smoothie_drivers/__init__.py +4 -0
  73. opentrons/drivers/smoothie_drivers/connection.py +51 -0
  74. opentrons/drivers/smoothie_drivers/constants.py +121 -0
  75. opentrons/drivers/smoothie_drivers/driver_3_0.py +1933 -0
  76. opentrons/drivers/smoothie_drivers/errors.py +49 -0
  77. opentrons/drivers/smoothie_drivers/parse_utils.py +143 -0
  78. opentrons/drivers/smoothie_drivers/simulator.py +99 -0
  79. opentrons/drivers/smoothie_drivers/types.py +16 -0
  80. opentrons/drivers/temp_deck/__init__.py +10 -0
  81. opentrons/drivers/temp_deck/abstract.py +54 -0
  82. opentrons/drivers/temp_deck/driver.py +197 -0
  83. opentrons/drivers/temp_deck/simulator.py +57 -0
  84. opentrons/drivers/thermocycler/__init__.py +12 -0
  85. opentrons/drivers/thermocycler/abstract.py +99 -0
  86. opentrons/drivers/thermocycler/driver.py +395 -0
  87. opentrons/drivers/thermocycler/simulator.py +126 -0
  88. opentrons/drivers/types.py +107 -0
  89. opentrons/drivers/utils.py +222 -0
  90. opentrons/execute.py +742 -0
  91. opentrons/hardware_control/__init__.py +65 -0
  92. opentrons/hardware_control/__main__.py +77 -0
  93. opentrons/hardware_control/adapters.py +98 -0
  94. opentrons/hardware_control/api.py +1347 -0
  95. opentrons/hardware_control/backends/__init__.py +7 -0
  96. opentrons/hardware_control/backends/controller.py +400 -0
  97. opentrons/hardware_control/backends/errors.py +9 -0
  98. opentrons/hardware_control/backends/estop_state.py +164 -0
  99. opentrons/hardware_control/backends/flex_protocol.py +497 -0
  100. opentrons/hardware_control/backends/ot3controller.py +1930 -0
  101. opentrons/hardware_control/backends/ot3simulator.py +900 -0
  102. opentrons/hardware_control/backends/ot3utils.py +664 -0
  103. opentrons/hardware_control/backends/simulator.py +442 -0
  104. opentrons/hardware_control/backends/status_bar_state.py +240 -0
  105. opentrons/hardware_control/backends/subsystem_manager.py +431 -0
  106. opentrons/hardware_control/backends/tip_presence_manager.py +173 -0
  107. opentrons/hardware_control/backends/types.py +14 -0
  108. opentrons/hardware_control/constants.py +6 -0
  109. opentrons/hardware_control/dev_types.py +125 -0
  110. opentrons/hardware_control/emulation/__init__.py +0 -0
  111. opentrons/hardware_control/emulation/abstract_emulator.py +21 -0
  112. opentrons/hardware_control/emulation/app.py +56 -0
  113. opentrons/hardware_control/emulation/connection_handler.py +38 -0
  114. opentrons/hardware_control/emulation/heater_shaker.py +150 -0
  115. opentrons/hardware_control/emulation/magdeck.py +60 -0
  116. opentrons/hardware_control/emulation/module_server/__init__.py +8 -0
  117. opentrons/hardware_control/emulation/module_server/client.py +78 -0
  118. opentrons/hardware_control/emulation/module_server/helpers.py +130 -0
  119. opentrons/hardware_control/emulation/module_server/models.py +31 -0
  120. opentrons/hardware_control/emulation/module_server/server.py +110 -0
  121. opentrons/hardware_control/emulation/parser.py +74 -0
  122. opentrons/hardware_control/emulation/proxy.py +241 -0
  123. opentrons/hardware_control/emulation/run_emulator.py +68 -0
  124. opentrons/hardware_control/emulation/scripts/__init__.py +0 -0
  125. opentrons/hardware_control/emulation/scripts/run_app.py +54 -0
  126. opentrons/hardware_control/emulation/scripts/run_module_emulator.py +72 -0
  127. opentrons/hardware_control/emulation/scripts/run_smoothie.py +37 -0
  128. opentrons/hardware_control/emulation/settings.py +119 -0
  129. opentrons/hardware_control/emulation/simulations.py +133 -0
  130. opentrons/hardware_control/emulation/smoothie.py +192 -0
  131. opentrons/hardware_control/emulation/tempdeck.py +69 -0
  132. opentrons/hardware_control/emulation/thermocycler.py +128 -0
  133. opentrons/hardware_control/emulation/types.py +10 -0
  134. opentrons/hardware_control/emulation/util.py +38 -0
  135. opentrons/hardware_control/errors.py +43 -0
  136. opentrons/hardware_control/execution_manager.py +164 -0
  137. opentrons/hardware_control/instruments/__init__.py +5 -0
  138. opentrons/hardware_control/instruments/instrument_abc.py +39 -0
  139. opentrons/hardware_control/instruments/ot2/__init__.py +0 -0
  140. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +152 -0
  141. opentrons/hardware_control/instruments/ot2/pipette.py +777 -0
  142. opentrons/hardware_control/instruments/ot2/pipette_handler.py +995 -0
  143. opentrons/hardware_control/instruments/ot3/__init__.py +0 -0
  144. opentrons/hardware_control/instruments/ot3/gripper.py +420 -0
  145. opentrons/hardware_control/instruments/ot3/gripper_handler.py +173 -0
  146. opentrons/hardware_control/instruments/ot3/instrument_calibration.py +214 -0
  147. opentrons/hardware_control/instruments/ot3/pipette.py +858 -0
  148. opentrons/hardware_control/instruments/ot3/pipette_handler.py +1030 -0
  149. opentrons/hardware_control/module_control.py +332 -0
  150. opentrons/hardware_control/modules/__init__.py +69 -0
  151. opentrons/hardware_control/modules/absorbance_reader.py +373 -0
  152. opentrons/hardware_control/modules/errors.py +7 -0
  153. opentrons/hardware_control/modules/flex_stacker.py +948 -0
  154. opentrons/hardware_control/modules/heater_shaker.py +426 -0
  155. opentrons/hardware_control/modules/lid_temp_status.py +35 -0
  156. opentrons/hardware_control/modules/magdeck.py +233 -0
  157. opentrons/hardware_control/modules/mod_abc.py +245 -0
  158. opentrons/hardware_control/modules/module_calibration.py +93 -0
  159. opentrons/hardware_control/modules/plate_temp_status.py +61 -0
  160. opentrons/hardware_control/modules/tempdeck.py +299 -0
  161. opentrons/hardware_control/modules/thermocycler.py +731 -0
  162. opentrons/hardware_control/modules/types.py +417 -0
  163. opentrons/hardware_control/modules/update.py +255 -0
  164. opentrons/hardware_control/modules/utils.py +73 -0
  165. opentrons/hardware_control/motion_utilities.py +318 -0
  166. opentrons/hardware_control/nozzle_manager.py +422 -0
  167. opentrons/hardware_control/ot3_calibration.py +1171 -0
  168. opentrons/hardware_control/ot3api.py +3227 -0
  169. opentrons/hardware_control/pause_manager.py +31 -0
  170. opentrons/hardware_control/poller.py +112 -0
  171. opentrons/hardware_control/protocols/__init__.py +106 -0
  172. opentrons/hardware_control/protocols/asyncio_configurable.py +11 -0
  173. opentrons/hardware_control/protocols/calibratable.py +45 -0
  174. opentrons/hardware_control/protocols/chassis_accessory_manager.py +90 -0
  175. opentrons/hardware_control/protocols/configurable.py +48 -0
  176. opentrons/hardware_control/protocols/event_sourcer.py +18 -0
  177. opentrons/hardware_control/protocols/execution_controllable.py +33 -0
  178. opentrons/hardware_control/protocols/flex_calibratable.py +96 -0
  179. opentrons/hardware_control/protocols/flex_instrument_configurer.py +52 -0
  180. opentrons/hardware_control/protocols/gripper_controller.py +55 -0
  181. opentrons/hardware_control/protocols/hardware_manager.py +51 -0
  182. opentrons/hardware_control/protocols/identifiable.py +16 -0
  183. opentrons/hardware_control/protocols/instrument_configurer.py +206 -0
  184. opentrons/hardware_control/protocols/liquid_handler.py +266 -0
  185. opentrons/hardware_control/protocols/module_provider.py +16 -0
  186. opentrons/hardware_control/protocols/motion_controller.py +243 -0
  187. opentrons/hardware_control/protocols/position_estimator.py +45 -0
  188. opentrons/hardware_control/protocols/simulatable.py +10 -0
  189. opentrons/hardware_control/protocols/stoppable.py +9 -0
  190. opentrons/hardware_control/protocols/types.py +27 -0
  191. opentrons/hardware_control/robot_calibration.py +224 -0
  192. opentrons/hardware_control/scripts/README.md +28 -0
  193. opentrons/hardware_control/scripts/__init__.py +1 -0
  194. opentrons/hardware_control/scripts/gripper_control.py +208 -0
  195. opentrons/hardware_control/scripts/ot3gripper +7 -0
  196. opentrons/hardware_control/scripts/ot3repl +7 -0
  197. opentrons/hardware_control/scripts/repl.py +187 -0
  198. opentrons/hardware_control/scripts/tc_control.py +97 -0
  199. opentrons/hardware_control/simulator_setup.py +260 -0
  200. opentrons/hardware_control/thread_manager.py +431 -0
  201. opentrons/hardware_control/threaded_async_lock.py +97 -0
  202. opentrons/hardware_control/types.py +792 -0
  203. opentrons/hardware_control/util.py +234 -0
  204. opentrons/legacy_broker.py +53 -0
  205. opentrons/legacy_commands/__init__.py +1 -0
  206. opentrons/legacy_commands/commands.py +483 -0
  207. opentrons/legacy_commands/helpers.py +153 -0
  208. opentrons/legacy_commands/module_commands.py +215 -0
  209. opentrons/legacy_commands/protocol_commands.py +54 -0
  210. opentrons/legacy_commands/publisher.py +155 -0
  211. opentrons/legacy_commands/robot_commands.py +51 -0
  212. opentrons/legacy_commands/types.py +1115 -0
  213. opentrons/motion_planning/__init__.py +32 -0
  214. opentrons/motion_planning/adjacent_slots_getters.py +168 -0
  215. opentrons/motion_planning/deck_conflict.py +396 -0
  216. opentrons/motion_planning/errors.py +35 -0
  217. opentrons/motion_planning/types.py +42 -0
  218. opentrons/motion_planning/waypoints.py +218 -0
  219. opentrons/ordered_set.py +138 -0
  220. opentrons/protocol_api/__init__.py +105 -0
  221. opentrons/protocol_api/_liquid.py +157 -0
  222. opentrons/protocol_api/_liquid_properties.py +814 -0
  223. opentrons/protocol_api/_nozzle_layout.py +31 -0
  224. opentrons/protocol_api/_parameter_context.py +300 -0
  225. opentrons/protocol_api/_parameters.py +31 -0
  226. opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
  227. opentrons/protocol_api/_types.py +43 -0
  228. opentrons/protocol_api/config.py +23 -0
  229. opentrons/protocol_api/core/__init__.py +23 -0
  230. opentrons/protocol_api/core/common.py +33 -0
  231. opentrons/protocol_api/core/core_map.py +74 -0
  232. opentrons/protocol_api/core/engine/__init__.py +22 -0
  233. opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
  234. opentrons/protocol_api/core/engine/deck_conflict.py +348 -0
  235. opentrons/protocol_api/core/engine/exceptions.py +19 -0
  236. opentrons/protocol_api/core/engine/instrument.py +2391 -0
  237. opentrons/protocol_api/core/engine/labware.py +238 -0
  238. opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
  239. opentrons/protocol_api/core/engine/module_core.py +1025 -0
  240. opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
  241. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
  242. opentrons/protocol_api/core/engine/point_calculations.py +64 -0
  243. opentrons/protocol_api/core/engine/protocol.py +1153 -0
  244. opentrons/protocol_api/core/engine/robot.py +139 -0
  245. opentrons/protocol_api/core/engine/stringify.py +74 -0
  246. opentrons/protocol_api/core/engine/transfer_components_executor.py +990 -0
  247. opentrons/protocol_api/core/engine/well.py +241 -0
  248. opentrons/protocol_api/core/instrument.py +459 -0
  249. opentrons/protocol_api/core/labware.py +151 -0
  250. opentrons/protocol_api/core/legacy/__init__.py +11 -0
  251. opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
  252. opentrons/protocol_api/core/legacy/deck.py +369 -0
  253. opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
  254. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
  255. opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
  256. opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
  257. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
  258. opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
  259. opentrons/protocol_api/core/legacy/load_info.py +67 -0
  260. opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
  261. opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
  262. opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
  263. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
  264. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
  265. opentrons/protocol_api/core/module.py +484 -0
  266. opentrons/protocol_api/core/protocol.py +311 -0
  267. opentrons/protocol_api/core/robot.py +51 -0
  268. opentrons/protocol_api/core/well.py +116 -0
  269. opentrons/protocol_api/core/well_grid.py +45 -0
  270. opentrons/protocol_api/create_protocol_context.py +177 -0
  271. opentrons/protocol_api/deck.py +223 -0
  272. opentrons/protocol_api/disposal_locations.py +244 -0
  273. opentrons/protocol_api/instrument_context.py +3212 -0
  274. opentrons/protocol_api/labware.py +1579 -0
  275. opentrons/protocol_api/module_contexts.py +1425 -0
  276. opentrons/protocol_api/module_validation_and_errors.py +61 -0
  277. opentrons/protocol_api/protocol_context.py +1688 -0
  278. opentrons/protocol_api/robot_context.py +303 -0
  279. opentrons/protocol_api/validation.py +761 -0
  280. opentrons/protocol_engine/__init__.py +155 -0
  281. opentrons/protocol_engine/actions/__init__.py +65 -0
  282. opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
  283. opentrons/protocol_engine/actions/action_handler.py +13 -0
  284. opentrons/protocol_engine/actions/actions.py +302 -0
  285. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  286. opentrons/protocol_engine/clients/__init__.py +5 -0
  287. opentrons/protocol_engine/clients/sync_client.py +174 -0
  288. opentrons/protocol_engine/clients/transports.py +197 -0
  289. opentrons/protocol_engine/commands/__init__.py +757 -0
  290. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
  291. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
  292. opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
  293. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
  294. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
  295. opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
  296. opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
  297. opentrons/protocol_engine/commands/aspirate.py +244 -0
  298. opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
  299. opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
  300. opentrons/protocol_engine/commands/blow_out.py +146 -0
  301. opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
  302. opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
  303. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
  304. opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
  305. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
  306. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
  307. opentrons/protocol_engine/commands/command.py +308 -0
  308. opentrons/protocol_engine/commands/command_unions.py +974 -0
  309. opentrons/protocol_engine/commands/comment.py +57 -0
  310. opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
  311. opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
  312. opentrons/protocol_engine/commands/custom.py +67 -0
  313. opentrons/protocol_engine/commands/dispense.py +194 -0
  314. opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
  315. opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
  316. opentrons/protocol_engine/commands/drop_tip.py +232 -0
  317. opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
  318. opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
  319. opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
  320. opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
  321. opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
  322. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
  323. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
  324. opentrons/protocol_engine/commands/flex_stacker/store.py +326 -0
  325. opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
  326. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  327. opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
  328. opentrons/protocol_engine/commands/hash_command_params.py +38 -0
  329. opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
  330. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
  331. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
  332. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
  333. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
  334. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
  335. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
  336. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
  337. opentrons/protocol_engine/commands/home.py +100 -0
  338. opentrons/protocol_engine/commands/identify_module.py +86 -0
  339. opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
  340. opentrons/protocol_engine/commands/liquid_probe.py +464 -0
  341. opentrons/protocol_engine/commands/load_labware.py +210 -0
  342. opentrons/protocol_engine/commands/load_lid.py +154 -0
  343. opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
  344. opentrons/protocol_engine/commands/load_liquid.py +95 -0
  345. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  346. opentrons/protocol_engine/commands/load_module.py +223 -0
  347. opentrons/protocol_engine/commands/load_pipette.py +167 -0
  348. opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
  349. opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
  350. opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
  351. opentrons/protocol_engine/commands/move_labware.py +546 -0
  352. opentrons/protocol_engine/commands/move_relative.py +102 -0
  353. opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
  354. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
  355. opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
  356. opentrons/protocol_engine/commands/move_to_well.py +119 -0
  357. opentrons/protocol_engine/commands/movement_common.py +338 -0
  358. opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
  359. opentrons/protocol_engine/commands/pipetting_common.py +443 -0
  360. opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
  361. opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
  362. opentrons/protocol_engine/commands/reload_labware.py +90 -0
  363. opentrons/protocol_engine/commands/retract_axis.py +75 -0
  364. opentrons/protocol_engine/commands/robot/__init__.py +70 -0
  365. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -0
  366. opentrons/protocol_engine/commands/robot/common.py +18 -0
  367. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  368. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  369. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  370. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +86 -0
  371. opentrons/protocol_engine/commands/save_position.py +109 -0
  372. opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
  373. opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
  374. opentrons/protocol_engine/commands/set_status_bar.py +89 -0
  375. opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
  376. opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
  377. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
  378. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
  379. opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
  380. opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
  381. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
  382. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
  383. opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
  384. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
  385. opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
  386. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
  387. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
  388. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
  389. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
  390. opentrons/protocol_engine/commands/touch_tip.py +189 -0
  391. opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
  392. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
  393. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
  394. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
  395. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
  396. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
  397. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
  398. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
  399. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
  400. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
  401. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
  402. opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
  403. opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
  404. opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
  405. opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
  406. opentrons/protocol_engine/create_protocol_engine.py +193 -0
  407. opentrons/protocol_engine/engine_support.py +28 -0
  408. opentrons/protocol_engine/error_recovery_policy.py +81 -0
  409. opentrons/protocol_engine/errors/__init__.py +191 -0
  410. opentrons/protocol_engine/errors/error_occurrence.py +182 -0
  411. opentrons/protocol_engine/errors/exceptions.py +1308 -0
  412. opentrons/protocol_engine/execution/__init__.py +50 -0
  413. opentrons/protocol_engine/execution/command_executor.py +216 -0
  414. opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
  415. opentrons/protocol_engine/execution/door_watcher.py +119 -0
  416. opentrons/protocol_engine/execution/equipment.py +819 -0
  417. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  418. opentrons/protocol_engine/execution/gantry_mover.py +686 -0
  419. opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
  420. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
  421. opentrons/protocol_engine/execution/labware_movement.py +297 -0
  422. opentrons/protocol_engine/execution/movement.py +349 -0
  423. opentrons/protocol_engine/execution/pipetting.py +607 -0
  424. opentrons/protocol_engine/execution/queue_worker.py +86 -0
  425. opentrons/protocol_engine/execution/rail_lights.py +25 -0
  426. opentrons/protocol_engine/execution/run_control.py +33 -0
  427. opentrons/protocol_engine/execution/status_bar.py +34 -0
  428. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
  429. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
  430. opentrons/protocol_engine/execution/tip_handler.py +550 -0
  431. opentrons/protocol_engine/labware_offset_standardization.py +194 -0
  432. opentrons/protocol_engine/notes/__init__.py +17 -0
  433. opentrons/protocol_engine/notes/notes.py +59 -0
  434. opentrons/protocol_engine/plugins.py +104 -0
  435. opentrons/protocol_engine/protocol_engine.py +683 -0
  436. opentrons/protocol_engine/resources/__init__.py +26 -0
  437. opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
  438. opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
  439. opentrons/protocol_engine/resources/file_provider.py +161 -0
  440. opentrons/protocol_engine/resources/fixture_validation.py +58 -0
  441. opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
  442. opentrons/protocol_engine/resources/labware_validation.py +73 -0
  443. opentrons/protocol_engine/resources/model_utils.py +32 -0
  444. opentrons/protocol_engine/resources/module_data_provider.py +44 -0
  445. opentrons/protocol_engine/resources/ot3_validation.py +21 -0
  446. opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
  447. opentrons/protocol_engine/slot_standardization.py +128 -0
  448. opentrons/protocol_engine/state/__init__.py +1 -0
  449. opentrons/protocol_engine/state/_abstract_store.py +27 -0
  450. opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
  451. opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
  452. opentrons/protocol_engine/state/_move_types.py +83 -0
  453. opentrons/protocol_engine/state/_well_math.py +193 -0
  454. opentrons/protocol_engine/state/addressable_areas.py +699 -0
  455. opentrons/protocol_engine/state/command_history.py +309 -0
  456. opentrons/protocol_engine/state/commands.py +1158 -0
  457. opentrons/protocol_engine/state/config.py +39 -0
  458. opentrons/protocol_engine/state/files.py +57 -0
  459. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  460. opentrons/protocol_engine/state/geometry.py +2359 -0
  461. opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
  462. opentrons/protocol_engine/state/labware.py +1459 -0
  463. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  464. opentrons/protocol_engine/state/liquids.py +73 -0
  465. opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
  466. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
  467. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
  468. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
  469. opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
  470. opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
  471. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
  472. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
  473. opentrons/protocol_engine/state/modules.py +1500 -0
  474. opentrons/protocol_engine/state/motion.py +373 -0
  475. opentrons/protocol_engine/state/pipettes.py +905 -0
  476. opentrons/protocol_engine/state/state.py +421 -0
  477. opentrons/protocol_engine/state/state_summary.py +36 -0
  478. opentrons/protocol_engine/state/tips.py +420 -0
  479. opentrons/protocol_engine/state/update_types.py +904 -0
  480. opentrons/protocol_engine/state/wells.py +290 -0
  481. opentrons/protocol_engine/types/__init__.py +308 -0
  482. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  483. opentrons/protocol_engine/types/command_annotations.py +53 -0
  484. opentrons/protocol_engine/types/deck_configuration.py +81 -0
  485. opentrons/protocol_engine/types/execution.py +96 -0
  486. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  487. opentrons/protocol_engine/types/instrument.py +47 -0
  488. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  489. opentrons/protocol_engine/types/labware.py +131 -0
  490. opentrons/protocol_engine/types/labware_movement.py +22 -0
  491. opentrons/protocol_engine/types/labware_offset_location.py +111 -0
  492. opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
  493. opentrons/protocol_engine/types/liquid.py +40 -0
  494. opentrons/protocol_engine/types/liquid_class.py +59 -0
  495. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  496. opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
  497. opentrons/protocol_engine/types/location.py +194 -0
  498. opentrons/protocol_engine/types/module.py +303 -0
  499. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  500. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  501. opentrons/protocol_engine/types/tip.py +18 -0
  502. opentrons/protocol_engine/types/util.py +21 -0
  503. opentrons/protocol_engine/types/well_position.py +124 -0
  504. opentrons/protocol_reader/__init__.py +37 -0
  505. opentrons/protocol_reader/extract_labware_definitions.py +66 -0
  506. opentrons/protocol_reader/file_format_validator.py +152 -0
  507. opentrons/protocol_reader/file_hasher.py +27 -0
  508. opentrons/protocol_reader/file_identifier.py +284 -0
  509. opentrons/protocol_reader/file_reader_writer.py +90 -0
  510. opentrons/protocol_reader/input_file.py +16 -0
  511. opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
  512. opentrons/protocol_reader/protocol_reader.py +188 -0
  513. opentrons/protocol_reader/protocol_source.py +124 -0
  514. opentrons/protocol_reader/role_analyzer.py +86 -0
  515. opentrons/protocol_runner/__init__.py +26 -0
  516. opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
  517. opentrons/protocol_runner/json_file_reader.py +55 -0
  518. opentrons/protocol_runner/json_translator.py +314 -0
  519. opentrons/protocol_runner/legacy_command_mapper.py +848 -0
  520. opentrons/protocol_runner/legacy_context_plugin.py +116 -0
  521. opentrons/protocol_runner/protocol_runner.py +530 -0
  522. opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
  523. opentrons/protocol_runner/run_orchestrator.py +496 -0
  524. opentrons/protocol_runner/task_queue.py +95 -0
  525. opentrons/protocols/__init__.py +6 -0
  526. opentrons/protocols/advanced_control/__init__.py +0 -0
  527. opentrons/protocols/advanced_control/common.py +38 -0
  528. opentrons/protocols/advanced_control/mix.py +60 -0
  529. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  530. opentrons/protocols/advanced_control/transfers/common.py +180 -0
  531. opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
  532. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
  533. opentrons/protocols/api_support/__init__.py +0 -0
  534. opentrons/protocols/api_support/constants.py +8 -0
  535. opentrons/protocols/api_support/deck_type.py +110 -0
  536. opentrons/protocols/api_support/definitions.py +18 -0
  537. opentrons/protocols/api_support/instrument.py +151 -0
  538. opentrons/protocols/api_support/labware_like.py +233 -0
  539. opentrons/protocols/api_support/tip_tracker.py +175 -0
  540. opentrons/protocols/api_support/types.py +32 -0
  541. opentrons/protocols/api_support/util.py +403 -0
  542. opentrons/protocols/bundle.py +89 -0
  543. opentrons/protocols/duration/__init__.py +4 -0
  544. opentrons/protocols/duration/errors.py +5 -0
  545. opentrons/protocols/duration/estimator.py +628 -0
  546. opentrons/protocols/execution/__init__.py +0 -0
  547. opentrons/protocols/execution/dev_types.py +181 -0
  548. opentrons/protocols/execution/errors.py +40 -0
  549. opentrons/protocols/execution/execute.py +84 -0
  550. opentrons/protocols/execution/execute_json_v3.py +275 -0
  551. opentrons/protocols/execution/execute_json_v4.py +359 -0
  552. opentrons/protocols/execution/execute_json_v5.py +28 -0
  553. opentrons/protocols/execution/execute_python.py +169 -0
  554. opentrons/protocols/execution/json_dispatchers.py +87 -0
  555. opentrons/protocols/execution/types.py +7 -0
  556. opentrons/protocols/geometry/__init__.py +0 -0
  557. opentrons/protocols/geometry/planning.py +297 -0
  558. opentrons/protocols/labware.py +312 -0
  559. opentrons/protocols/models/__init__.py +0 -0
  560. opentrons/protocols/models/json_protocol.py +679 -0
  561. opentrons/protocols/parameters/__init__.py +0 -0
  562. opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
  563. opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
  564. opentrons/protocols/parameters/exceptions.py +34 -0
  565. opentrons/protocols/parameters/parameter_definition.py +272 -0
  566. opentrons/protocols/parameters/types.py +17 -0
  567. opentrons/protocols/parameters/validation.py +267 -0
  568. opentrons/protocols/parse.py +671 -0
  569. opentrons/protocols/types.py +159 -0
  570. opentrons/py.typed +0 -0
  571. opentrons/resources/scripts/lpc21isp +0 -0
  572. opentrons/resources/smoothie-edge-8414642.hex +23010 -0
  573. opentrons/simulate.py +1065 -0
  574. opentrons/system/__init__.py +6 -0
  575. opentrons/system/camera.py +51 -0
  576. opentrons/system/log_control.py +59 -0
  577. opentrons/system/nmcli.py +856 -0
  578. opentrons/system/resin.py +24 -0
  579. opentrons/system/smoothie_update.py +15 -0
  580. opentrons/system/wifi.py +204 -0
  581. opentrons/tools/__init__.py +0 -0
  582. opentrons/tools/args_handler.py +22 -0
  583. opentrons/tools/write_pipette_memory.py +157 -0
  584. opentrons/types.py +618 -0
  585. opentrons/util/__init__.py +1 -0
  586. opentrons/util/async_helpers.py +166 -0
  587. opentrons/util/broker.py +84 -0
  588. opentrons/util/change_notifier.py +47 -0
  589. opentrons/util/entrypoint_util.py +278 -0
  590. opentrons/util/get_union_elements.py +26 -0
  591. opentrons/util/helpers.py +6 -0
  592. opentrons/util/linal.py +178 -0
  593. opentrons/util/logging_config.py +265 -0
  594. opentrons/util/logging_queue_handler.py +61 -0
  595. opentrons/util/performance_helpers.py +157 -0
  596. opentrons-8.6.0a1.dist-info/METADATA +37 -0
  597. opentrons-8.6.0a1.dist-info/RECORD +600 -0
  598. opentrons-8.6.0a1.dist-info/WHEEL +4 -0
  599. opentrons-8.6.0a1.dist-info/entry_points.txt +3 -0
  600. opentrons-8.6.0a1.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,1500 @@
1
+ """Basic modules data state and store."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from dataclasses import dataclass
7
+ from typing import (
8
+ Dict,
9
+ List,
10
+ NamedTuple,
11
+ Optional,
12
+ Sequence,
13
+ Type,
14
+ TypeVar,
15
+ Union,
16
+ overload,
17
+ )
18
+ from numpy import array, dot, double as npdouble
19
+ from numpy.typing import NDArray
20
+
21
+ from opentrons.hardware_control.modules.magdeck import (
22
+ OFFSET_TO_LABWARE_BOTTOM as MAGNETIC_MODULE_OFFSET_TO_LABWARE_BOTTOM,
23
+ )
24
+ from opentrons.hardware_control.modules.types import LiveData
25
+ from opentrons.motion_planning.adjacent_slots_getters import (
26
+ get_east_slot,
27
+ get_west_slot,
28
+ get_adjacent_staging_slot,
29
+ )
30
+ from opentrons.protocol_engine.actions.get_state_update import get_state_updates
31
+ from opentrons.protocol_engine.commands.calibration.calibrate_module import (
32
+ CalibrateModuleResult,
33
+ )
34
+ from opentrons.protocol_engine.state import update_types
35
+ from opentrons.protocol_engine.state.module_substates.absorbance_reader_substate import (
36
+ AbsorbanceReaderMeasureMode,
37
+ )
38
+ from opentrons.types import DeckSlotName, MountType, Point, StagingSlotName
39
+ from .update_types import (
40
+ AbsorbanceReaderStateUpdate,
41
+ FlexStackerStateUpdate,
42
+ LoadModuleUpdate,
43
+ )
44
+ from ..errors import ModuleNotConnectedError, AreaNotInDeckConfigurationError
45
+ from ..resources import deck_configuration_provider
46
+
47
+ from ..types import (
48
+ LoadedModule,
49
+ ModuleModel,
50
+ ModuleOffsetVector,
51
+ ModuleOffsetData,
52
+ ModuleType,
53
+ ModuleDefinition,
54
+ DeckSlotLocation,
55
+ ModuleDimensions,
56
+ HeaterShakerLatchStatus,
57
+ HeaterShakerMovementRestrictors,
58
+ DeckType,
59
+ LabwareMovementOffsetData,
60
+ AddressableAreaLocation,
61
+ StackerStoredLabwareGroup,
62
+ )
63
+
64
+ from ..resources import DeckFixedLabware
65
+ from .addressable_areas import AddressableAreaView
66
+ from .. import errors
67
+ from ..commands import (
68
+ Command,
69
+ heater_shaker,
70
+ temperature_module,
71
+ thermocycler,
72
+ )
73
+ from ..actions import (
74
+ Action,
75
+ SucceedCommandAction,
76
+ AddModuleAction,
77
+ )
78
+ from ._abstract_store import HasState, HandlesActions
79
+ from .module_substates import (
80
+ MagneticModuleSubState,
81
+ HeaterShakerModuleSubState,
82
+ TemperatureModuleSubState,
83
+ ThermocyclerModuleSubState,
84
+ AbsorbanceReaderSubState,
85
+ FlexStackerSubState,
86
+ MagneticModuleId,
87
+ HeaterShakerModuleId,
88
+ TemperatureModuleId,
89
+ ThermocyclerModuleId,
90
+ AbsorbanceReaderId,
91
+ FlexStackerId,
92
+ MagneticBlockSubState,
93
+ MagneticBlockId,
94
+ ModuleSubStateType,
95
+ )
96
+ from .config import Config
97
+
98
+
99
+ ModuleSubStateT = TypeVar("ModuleSubStateT", bound=ModuleSubStateType)
100
+
101
+
102
+ class SlotTransit(NamedTuple):
103
+ """Class defining starting and ending slots in a pipette movement."""
104
+
105
+ start: DeckSlotName
106
+ end: DeckSlotName
107
+
108
+
109
+ _OT2_THERMOCYCLER_SLOT_TRANSITS_TO_DODGE = {
110
+ SlotTransit(start=DeckSlotName.SLOT_1, end=DeckSlotName.FIXED_TRASH),
111
+ SlotTransit(start=DeckSlotName.FIXED_TRASH, end=DeckSlotName.SLOT_1),
112
+ SlotTransit(start=DeckSlotName.SLOT_4, end=DeckSlotName.FIXED_TRASH),
113
+ SlotTransit(start=DeckSlotName.FIXED_TRASH, end=DeckSlotName.SLOT_4),
114
+ SlotTransit(start=DeckSlotName.SLOT_4, end=DeckSlotName.SLOT_9),
115
+ SlotTransit(start=DeckSlotName.SLOT_9, end=DeckSlotName.SLOT_4),
116
+ SlotTransit(start=DeckSlotName.SLOT_4, end=DeckSlotName.SLOT_8),
117
+ SlotTransit(start=DeckSlotName.SLOT_8, end=DeckSlotName.SLOT_4),
118
+ SlotTransit(start=DeckSlotName.SLOT_1, end=DeckSlotName.SLOT_8),
119
+ SlotTransit(start=DeckSlotName.SLOT_8, end=DeckSlotName.SLOT_1),
120
+ SlotTransit(start=DeckSlotName.SLOT_4, end=DeckSlotName.SLOT_11),
121
+ SlotTransit(start=DeckSlotName.SLOT_11, end=DeckSlotName.SLOT_4),
122
+ SlotTransit(start=DeckSlotName.SLOT_1, end=DeckSlotName.SLOT_11),
123
+ SlotTransit(start=DeckSlotName.SLOT_11, end=DeckSlotName.SLOT_1),
124
+ }
125
+
126
+ _OT3_THERMOCYCLER_SLOT_TRANSITS_TO_DODGE = {
127
+ SlotTransit(start=t.start.to_ot3_equivalent(), end=t.end.to_ot3_equivalent())
128
+ for t in _OT2_THERMOCYCLER_SLOT_TRANSITS_TO_DODGE
129
+ }
130
+
131
+ _THERMOCYCLER_SLOT_TRANSITS_TO_DODGE = (
132
+ _OT2_THERMOCYCLER_SLOT_TRANSITS_TO_DODGE | _OT3_THERMOCYCLER_SLOT_TRANSITS_TO_DODGE
133
+ )
134
+
135
+ _THERMOCYCLER_SLOT = DeckSlotName.SLOT_B1
136
+ _OT2_THERMOCYCLER_ADDITIONAL_SLOTS = [
137
+ DeckSlotName.SLOT_8,
138
+ DeckSlotName.SLOT_10,
139
+ DeckSlotName.SLOT_11,
140
+ ]
141
+ _OT3_THERMOCYCLER_ADDITIONAL_SLOTS = [DeckSlotName.SLOT_A1]
142
+
143
+ _COLUMN_4_MODULES = [ModuleModel.FLEX_STACKER_MODULE_V1]
144
+
145
+
146
+ @dataclass(frozen=True)
147
+ class HardwareModule:
148
+ """Data describing an actually connected module."""
149
+
150
+ serial_number: Optional[str]
151
+ definition: ModuleDefinition
152
+
153
+
154
+ @dataclass
155
+ class ModuleState:
156
+ """The internal data to keep track of loaded modules."""
157
+
158
+ load_location_by_module_id: Dict[str, Optional[str]]
159
+ """The Cutout ID of the cutout (Flex) or slot (OT-2) that each module has been loaded.
160
+
161
+ This will be None when the module was added via
162
+ ProtocolEngine.use_attached_modules() instead of an explicit loadModule command.
163
+ AddressableAreaLocation is used to represent a literal Deck Slot for OT-2 locations.
164
+ The CutoutID string for a given Cutout that a Module Fixture is loaded into is used
165
+ for Flex. The type distinction is in place for implementation seperation between the two.
166
+ """
167
+
168
+ additional_slots_occupied_by_module_id: Dict[str, List[DeckSlotName]]
169
+ """List of additional slots occupied by each module.
170
+
171
+ The thermocycler (both GENs), occupies multiple slots on both OT-2 and the Flex
172
+ but only one slot is associated with the location of the thermocycler.
173
+ In order to check for deck conflicts with other items, we will keep track of any
174
+ additional slots occupied by a module here.
175
+
176
+ This will be None when a module occupies only one slot.
177
+ """
178
+
179
+ requested_model_by_id: Dict[str, Optional[ModuleModel]]
180
+ """The model by which each loaded module was requested.
181
+
182
+ Becuse of module compatibility, this can differ from the model found through
183
+ hardware_module_by_id. See `ModuleView.get_requested_model()` versus
184
+ `ModuleView.get_connected_model()`.
185
+
186
+ This will be None when the module was added via
187
+ ProtocolEngine.use_attached_modules() instead of an explicit loadModule command.
188
+ """
189
+
190
+ hardware_by_module_id: Dict[str, HardwareModule]
191
+ """Information about each module's physical hardware."""
192
+
193
+ substate_by_module_id: Dict[str, ModuleSubStateType]
194
+ """Information about each module that's specific to the module type."""
195
+
196
+ module_offset_by_serial: Dict[str, ModuleOffsetData]
197
+ """Information about each modules offsets."""
198
+
199
+ deck_type: DeckType
200
+ """Type of deck that the modules are on."""
201
+
202
+ deck_fixed_labware: Sequence[DeckFixedLabware]
203
+ """Fixed labware from the deck which may be assigned to a module.
204
+
205
+ The Opentrons Plate Reader module makes use of an electronic Lid labware which moves
206
+ between the Reader and Dock positions, and is pre-loaded into the engine as to persist
207
+ even when not in use. For this reason, we inject it here when an appropriate match
208
+ is identified.
209
+ """
210
+
211
+
212
+ class ModuleStore(HasState[ModuleState], HandlesActions):
213
+ """Module state container."""
214
+
215
+ _state: ModuleState
216
+
217
+ def __init__(
218
+ self,
219
+ config: Config,
220
+ deck_fixed_labware: Sequence[DeckFixedLabware],
221
+ module_calibration_offsets: Optional[Dict[str, ModuleOffsetData]] = None,
222
+ ) -> None:
223
+ """Initialize a ModuleStore and its state."""
224
+ self._state = ModuleState(
225
+ load_location_by_module_id={},
226
+ additional_slots_occupied_by_module_id={},
227
+ requested_model_by_id={},
228
+ hardware_by_module_id={},
229
+ substate_by_module_id={},
230
+ module_offset_by_serial=module_calibration_offsets or {},
231
+ deck_type=config.deck_type,
232
+ deck_fixed_labware=deck_fixed_labware,
233
+ )
234
+ self._robot_type = config.robot_type
235
+
236
+ def handle_action(self, action: Action) -> None:
237
+ """Modify state in reaction to an action."""
238
+ if isinstance(action, SucceedCommandAction):
239
+ self._handle_command(action.command)
240
+
241
+ elif isinstance(action, AddModuleAction):
242
+ self._add_module_substate(
243
+ module_id=action.module_id,
244
+ definition=action.definition,
245
+ serial_number=action.serial_number,
246
+ slot_name=None,
247
+ requested_model=None,
248
+ module_live_data=action.module_live_data,
249
+ )
250
+
251
+ for state_update in get_state_updates(action):
252
+ self._handle_state_update(state_update)
253
+
254
+ def _handle_command(self, command: Command) -> None:
255
+ # todo(mm, 2024-11-04): Delete this function. Port these isinstance()
256
+ # checks to the update_types.StateUpdate mechanism.
257
+
258
+ if isinstance(command.result, CalibrateModuleResult):
259
+ self._update_module_calibration(
260
+ module_id=command.params.moduleId,
261
+ module_offset=command.result.moduleOffset,
262
+ location=command.result.location,
263
+ )
264
+
265
+ if isinstance(
266
+ command.result,
267
+ (
268
+ heater_shaker.SetTargetTemperatureResult,
269
+ heater_shaker.DeactivateHeaterResult,
270
+ heater_shaker.SetAndWaitForShakeSpeedResult,
271
+ heater_shaker.DeactivateShakerResult,
272
+ heater_shaker.OpenLabwareLatchResult,
273
+ heater_shaker.CloseLabwareLatchResult,
274
+ ),
275
+ ):
276
+ self._handle_heater_shaker_commands(command)
277
+
278
+ if isinstance(
279
+ command.result,
280
+ (
281
+ temperature_module.SetTargetTemperatureResult,
282
+ temperature_module.DeactivateTemperatureResult,
283
+ ),
284
+ ):
285
+ self._handle_temperature_module_commands(command)
286
+
287
+ if isinstance(
288
+ command.result,
289
+ (
290
+ thermocycler.SetTargetBlockTemperatureResult,
291
+ thermocycler.DeactivateBlockResult,
292
+ thermocycler.SetTargetLidTemperatureResult,
293
+ thermocycler.DeactivateLidResult,
294
+ thermocycler.OpenLidResult,
295
+ thermocycler.CloseLidResult,
296
+ ),
297
+ ):
298
+ self._handle_thermocycler_module_commands(command)
299
+
300
+ def _handle_state_update(self, state_update: update_types.StateUpdate) -> None:
301
+ if state_update.loaded_module != update_types.NO_CHANGE:
302
+ self._handle_load_module(state_update.loaded_module)
303
+
304
+ if state_update.absorbance_reader_state_update != update_types.NO_CHANGE:
305
+ self._handle_absorbance_reader_commands(
306
+ state_update.absorbance_reader_state_update
307
+ )
308
+ if state_update.flex_stacker_state_update != update_types.NO_CHANGE:
309
+ self._handle_flex_stacker_commands(state_update.flex_stacker_state_update)
310
+
311
+ def _add_module_substate(
312
+ self,
313
+ module_id: str,
314
+ serial_number: Optional[str],
315
+ definition: ModuleDefinition,
316
+ slot_name: Optional[DeckSlotName],
317
+ requested_model: Optional[ModuleModel],
318
+ module_live_data: Optional[LiveData],
319
+ ) -> None:
320
+ # Loading slot name to Cutout ID (Flex)(OT-2) resolution
321
+ load_location: Optional[str]
322
+ if slot_name is not None:
323
+ load_location = deck_configuration_provider.get_cutout_id_by_deck_slot_name(
324
+ slot_name
325
+ )
326
+ else:
327
+ load_location = slot_name
328
+
329
+ actual_model = definition.model
330
+ live_data = module_live_data["data"] if module_live_data else None
331
+ self._state.requested_model_by_id[module_id] = requested_model
332
+ self._state.load_location_by_module_id[module_id] = load_location
333
+ self._state.hardware_by_module_id[module_id] = HardwareModule(
334
+ serial_number=serial_number,
335
+ definition=definition,
336
+ )
337
+
338
+ if ModuleModel.is_magnetic_module_model(actual_model):
339
+ self._state.substate_by_module_id[module_id] = MagneticModuleSubState(
340
+ module_id=MagneticModuleId(module_id),
341
+ model=actual_model,
342
+ )
343
+ elif ModuleModel.is_heater_shaker_module_model(actual_model):
344
+ self._state.substate_by_module_id[
345
+ module_id
346
+ ] = HeaterShakerModuleSubState.from_live_data(
347
+ module_id=HeaterShakerModuleId(module_id),
348
+ data=live_data,
349
+ )
350
+ elif ModuleModel.is_temperature_module_model(actual_model):
351
+ self._state.substate_by_module_id[
352
+ module_id
353
+ ] = TemperatureModuleSubState.from_live_data(
354
+ module_id=TemperatureModuleId(module_id),
355
+ data=live_data,
356
+ )
357
+ elif ModuleModel.is_thermocycler_module_model(actual_model):
358
+ self._state.substate_by_module_id[
359
+ module_id
360
+ ] = ThermocyclerModuleSubState.from_live_data(
361
+ module_id=ThermocyclerModuleId(module_id), data=live_data
362
+ )
363
+ self._update_additional_slots_occupied_by_thermocycler(
364
+ module_id=module_id, slot_name=slot_name
365
+ )
366
+ elif ModuleModel.is_magnetic_block(actual_model):
367
+ self._state.substate_by_module_id[module_id] = MagneticBlockSubState(
368
+ module_id=MagneticBlockId(module_id)
369
+ )
370
+ elif ModuleModel.is_absorbance_reader(actual_model):
371
+ self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
372
+ module_id=AbsorbanceReaderId(module_id),
373
+ configured=False,
374
+ measured=False,
375
+ is_lid_on=True,
376
+ data=None,
377
+ measure_mode=None,
378
+ configured_wavelengths=None,
379
+ reference_wavelength=None,
380
+ )
381
+ elif ModuleModel.is_flex_stacker(actual_model):
382
+ self._state.substate_by_module_id[module_id] = FlexStackerSubState(
383
+ module_id=FlexStackerId(module_id),
384
+ pool_primary_definition=None,
385
+ pool_adapter_definition=None,
386
+ pool_lid_definition=None,
387
+ contained_labware_bottom_first=[],
388
+ max_pool_count=0,
389
+ pool_overlap=0,
390
+ pool_height=0,
391
+ )
392
+
393
+ def _update_additional_slots_occupied_by_thermocycler(
394
+ self,
395
+ module_id: str,
396
+ slot_name: Optional[
397
+ DeckSlotName
398
+ ], # addModuleAction will not have a slot location
399
+ ) -> None:
400
+ if slot_name != _THERMOCYCLER_SLOT.to_equivalent_for_robot_type(
401
+ self._robot_type
402
+ ):
403
+ return
404
+
405
+ self._state.additional_slots_occupied_by_module_id[module_id] = (
406
+ _OT3_THERMOCYCLER_ADDITIONAL_SLOTS
407
+ if self._state.deck_type == DeckType.OT3_STANDARD
408
+ else _OT2_THERMOCYCLER_ADDITIONAL_SLOTS
409
+ )
410
+
411
+ def _update_module_calibration(
412
+ self,
413
+ module_id: str,
414
+ module_offset: ModuleOffsetVector,
415
+ location: DeckSlotLocation,
416
+ ) -> None:
417
+ module = self._state.hardware_by_module_id.get(module_id)
418
+ if module:
419
+ module_serial = module.serial_number
420
+ assert (
421
+ module_serial is not None
422
+ ), "Expected a module SN and got None instead."
423
+ self._state.module_offset_by_serial[module_serial] = ModuleOffsetData(
424
+ moduleOffsetVector=module_offset,
425
+ location=location,
426
+ )
427
+
428
+ def _handle_heater_shaker_commands(
429
+ self,
430
+ command: Union[
431
+ heater_shaker.SetTargetTemperature,
432
+ heater_shaker.DeactivateHeater,
433
+ heater_shaker.SetAndWaitForShakeSpeed,
434
+ heater_shaker.DeactivateShaker,
435
+ heater_shaker.OpenLabwareLatch,
436
+ heater_shaker.CloseLabwareLatch,
437
+ ],
438
+ ) -> None:
439
+ module_id = command.params.moduleId
440
+ hs_substate = self._state.substate_by_module_id[module_id]
441
+ assert isinstance(
442
+ hs_substate, HeaterShakerModuleSubState
443
+ ), f"{module_id} is not heater-shaker."
444
+
445
+ # Get current values to preserve target temperature not being set/deactivated
446
+ prev_state: HeaterShakerModuleSubState = hs_substate
447
+
448
+ if isinstance(command.result, heater_shaker.SetTargetTemperatureResult):
449
+ self._state.substate_by_module_id[module_id] = HeaterShakerModuleSubState(
450
+ module_id=HeaterShakerModuleId(module_id),
451
+ labware_latch_status=prev_state.labware_latch_status,
452
+ is_plate_shaking=prev_state.is_plate_shaking,
453
+ plate_target_temperature=command.params.celsius,
454
+ )
455
+ elif isinstance(command.result, heater_shaker.DeactivateHeaterResult):
456
+ self._state.substate_by_module_id[module_id] = HeaterShakerModuleSubState(
457
+ module_id=HeaterShakerModuleId(module_id),
458
+ labware_latch_status=prev_state.labware_latch_status,
459
+ is_plate_shaking=prev_state.is_plate_shaking,
460
+ plate_target_temperature=None,
461
+ )
462
+ elif isinstance(command.result, heater_shaker.SetAndWaitForShakeSpeedResult):
463
+ self._state.substate_by_module_id[module_id] = HeaterShakerModuleSubState(
464
+ module_id=HeaterShakerModuleId(module_id),
465
+ labware_latch_status=prev_state.labware_latch_status,
466
+ is_plate_shaking=True,
467
+ plate_target_temperature=prev_state.plate_target_temperature,
468
+ )
469
+ elif isinstance(command.result, heater_shaker.DeactivateShakerResult):
470
+ self._state.substate_by_module_id[module_id] = HeaterShakerModuleSubState(
471
+ module_id=HeaterShakerModuleId(module_id),
472
+ labware_latch_status=prev_state.labware_latch_status,
473
+ is_plate_shaking=False,
474
+ plate_target_temperature=prev_state.plate_target_temperature,
475
+ )
476
+ elif isinstance(command.result, heater_shaker.OpenLabwareLatchResult):
477
+ self._state.substate_by_module_id[module_id] = HeaterShakerModuleSubState(
478
+ module_id=HeaterShakerModuleId(module_id),
479
+ labware_latch_status=HeaterShakerLatchStatus.OPEN,
480
+ is_plate_shaking=prev_state.is_plate_shaking,
481
+ plate_target_temperature=prev_state.plate_target_temperature,
482
+ )
483
+ elif isinstance(command.result, heater_shaker.CloseLabwareLatchResult):
484
+ self._state.substate_by_module_id[module_id] = HeaterShakerModuleSubState(
485
+ module_id=HeaterShakerModuleId(module_id),
486
+ labware_latch_status=HeaterShakerLatchStatus.CLOSED,
487
+ is_plate_shaking=prev_state.is_plate_shaking,
488
+ plate_target_temperature=prev_state.plate_target_temperature,
489
+ )
490
+
491
+ def _handle_temperature_module_commands(
492
+ self,
493
+ command: Union[
494
+ temperature_module.SetTargetTemperature,
495
+ temperature_module.DeactivateTemperature,
496
+ ],
497
+ ) -> None:
498
+ module_id = command.params.moduleId
499
+ assert isinstance(
500
+ self._state.substate_by_module_id[module_id], TemperatureModuleSubState
501
+ ), f"{module_id} is not a temperature module."
502
+
503
+ if isinstance(command.result, temperature_module.SetTargetTemperatureResult):
504
+ self._state.substate_by_module_id[module_id] = TemperatureModuleSubState(
505
+ module_id=TemperatureModuleId(module_id),
506
+ plate_target_temperature=command.result.targetTemperature,
507
+ )
508
+ elif isinstance(command.result, temperature_module.DeactivateTemperatureResult):
509
+ self._state.substate_by_module_id[module_id] = TemperatureModuleSubState(
510
+ module_id=TemperatureModuleId(module_id),
511
+ plate_target_temperature=None,
512
+ )
513
+
514
+ def _handle_thermocycler_module_commands(
515
+ self,
516
+ command: Union[
517
+ thermocycler.SetTargetBlockTemperature,
518
+ thermocycler.DeactivateBlock,
519
+ thermocycler.SetTargetLidTemperature,
520
+ thermocycler.DeactivateLid,
521
+ thermocycler.OpenLid,
522
+ thermocycler.CloseLid,
523
+ ],
524
+ ) -> None:
525
+ module_id = command.params.moduleId
526
+ thermocycler_substate = self._state.substate_by_module_id[module_id]
527
+ assert isinstance(
528
+ thermocycler_substate, ThermocyclerModuleSubState
529
+ ), f"{module_id} is not a thermocycler module."
530
+
531
+ # Get current values to preserve target temperature not being set/deactivated
532
+ block_temperature = thermocycler_substate.target_block_temperature
533
+ lid_temperature = thermocycler_substate.target_lid_temperature
534
+ is_lid_open = thermocycler_substate.is_lid_open
535
+
536
+ if isinstance(command.result, thermocycler.SetTargetBlockTemperatureResult):
537
+ self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
538
+ module_id=ThermocyclerModuleId(module_id),
539
+ is_lid_open=is_lid_open,
540
+ target_block_temperature=command.result.targetBlockTemperature,
541
+ target_lid_temperature=lid_temperature,
542
+ )
543
+ elif isinstance(command.result, thermocycler.DeactivateBlockResult):
544
+ self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
545
+ module_id=ThermocyclerModuleId(module_id),
546
+ is_lid_open=is_lid_open,
547
+ target_block_temperature=None,
548
+ target_lid_temperature=lid_temperature,
549
+ )
550
+ elif isinstance(command.result, thermocycler.SetTargetLidTemperatureResult):
551
+ self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
552
+ module_id=ThermocyclerModuleId(module_id),
553
+ is_lid_open=is_lid_open,
554
+ target_block_temperature=block_temperature,
555
+ target_lid_temperature=command.result.targetLidTemperature,
556
+ )
557
+ elif isinstance(command.result, thermocycler.DeactivateLidResult):
558
+ self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
559
+ module_id=ThermocyclerModuleId(module_id),
560
+ is_lid_open=is_lid_open,
561
+ target_block_temperature=block_temperature,
562
+ target_lid_temperature=None,
563
+ )
564
+ elif isinstance(command.result, thermocycler.OpenLidResult):
565
+ self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
566
+ module_id=ThermocyclerModuleId(module_id),
567
+ is_lid_open=True,
568
+ target_block_temperature=block_temperature,
569
+ target_lid_temperature=lid_temperature,
570
+ )
571
+ elif isinstance(command.result, thermocycler.CloseLidResult):
572
+ self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
573
+ module_id=ThermocyclerModuleId(module_id),
574
+ is_lid_open=False,
575
+ target_block_temperature=block_temperature,
576
+ target_lid_temperature=lid_temperature,
577
+ )
578
+
579
+ def _handle_load_module(self, load_module_state_update: LoadModuleUpdate) -> None:
580
+ self._add_module_substate(
581
+ module_id=load_module_state_update.module_id,
582
+ definition=load_module_state_update.definition,
583
+ serial_number=load_module_state_update.serial_number,
584
+ slot_name=load_module_state_update.slot_name,
585
+ requested_model=load_module_state_update.requested_model,
586
+ module_live_data=None,
587
+ )
588
+
589
+ def _handle_absorbance_reader_commands(
590
+ self, absorbance_reader_state_update: AbsorbanceReaderStateUpdate
591
+ ) -> None:
592
+ # Get current values:
593
+ module_id = absorbance_reader_state_update.module_id
594
+ absorbance_reader_substate = self._state.substate_by_module_id[module_id]
595
+ assert isinstance(
596
+ absorbance_reader_substate, AbsorbanceReaderSubState
597
+ ), f"{module_id} is not an absorbance plate reader."
598
+ is_lid_on = absorbance_reader_substate.is_lid_on
599
+ measured = True
600
+ configured = absorbance_reader_substate.configured
601
+ measure_mode = absorbance_reader_substate.measure_mode
602
+ configured_wavelengths = absorbance_reader_substate.configured_wavelengths
603
+ reference_wavelength = absorbance_reader_substate.reference_wavelength
604
+ data = absorbance_reader_substate.data
605
+ if (
606
+ absorbance_reader_state_update.absorbance_reader_lid
607
+ != update_types.NO_CHANGE
608
+ ):
609
+ is_lid_on = absorbance_reader_state_update.absorbance_reader_lid.is_lid_on
610
+ elif (
611
+ absorbance_reader_state_update.initialize_absorbance_reader_update
612
+ != update_types.NO_CHANGE
613
+ ):
614
+ configured = True
615
+ measured = False
616
+ is_lid_on = is_lid_on
617
+ measure_mode = AbsorbanceReaderMeasureMode(
618
+ absorbance_reader_state_update.initialize_absorbance_reader_update.measure_mode
619
+ )
620
+ configured_wavelengths = (
621
+ absorbance_reader_state_update.initialize_absorbance_reader_update.sample_wave_lengths
622
+ )
623
+ reference_wavelength = (
624
+ absorbance_reader_state_update.initialize_absorbance_reader_update.reference_wave_length
625
+ )
626
+ data = None
627
+ elif (
628
+ absorbance_reader_state_update.absorbance_reader_data
629
+ != update_types.NO_CHANGE
630
+ ):
631
+ data = absorbance_reader_state_update.absorbance_reader_data.read_result
632
+ self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
633
+ module_id=AbsorbanceReaderId(module_id),
634
+ configured=configured,
635
+ measured=measured,
636
+ is_lid_on=is_lid_on,
637
+ measure_mode=measure_mode,
638
+ configured_wavelengths=configured_wavelengths,
639
+ reference_wavelength=reference_wavelength,
640
+ data=data,
641
+ )
642
+
643
+ def _handle_flex_stacker_commands(
644
+ self, state_update: FlexStackerStateUpdate
645
+ ) -> None:
646
+ """Handle Flex Stacker state updates."""
647
+ module_id = state_update.module_id
648
+ prev_substate = self._state.substate_by_module_id[module_id]
649
+ assert isinstance(
650
+ prev_substate, FlexStackerSubState
651
+ ), f"{module_id} is not a Flex Stacker."
652
+
653
+ self._state.substate_by_module_id[
654
+ module_id
655
+ ] = prev_substate.new_from_state_change(state_update)
656
+
657
+
658
+ class ModuleView:
659
+ """Read-only view of computed module state."""
660
+
661
+ _state: ModuleState
662
+
663
+ def __init__(self, state: ModuleState) -> None:
664
+ """Initialize the view with its backing state value."""
665
+ self._state = state
666
+
667
+ def get(self, module_id: str) -> LoadedModule:
668
+ """Get module data by the module's unique identifier."""
669
+ try:
670
+ load_location = self._state.load_location_by_module_id[module_id]
671
+ attached_module = self._state.hardware_by_module_id[module_id]
672
+
673
+ except KeyError as e:
674
+ raise errors.ModuleNotLoadedError(module_id=module_id) from e
675
+
676
+ slot_name = None
677
+ if isinstance(load_location, str):
678
+ slot_name = deck_configuration_provider.get_deck_slot_for_cutout_id(
679
+ load_location
680
+ )
681
+ location = (
682
+ DeckSlotLocation(slotName=slot_name) if slot_name is not None else None
683
+ )
684
+
685
+ return LoadedModule.model_construct(
686
+ id=module_id,
687
+ location=location,
688
+ model=attached_module.definition.model,
689
+ serialNumber=attached_module.serial_number,
690
+ )
691
+
692
+ def get_all(self) -> List[LoadedModule]:
693
+ """Get a list of all module entries in state."""
694
+ return [
695
+ self.get(mod_id) for mod_id in self._state.load_location_by_module_id.keys()
696
+ ]
697
+
698
+ def get_by_slot(
699
+ self,
700
+ slot_name: DeckSlotName,
701
+ ) -> Optional[LoadedModule]:
702
+ """Get the module located in a given slot, if any."""
703
+ locations_by_id = reversed(list(self._state.load_location_by_module_id.items()))
704
+
705
+ for module_id, load_location in locations_by_id:
706
+ module_slot: Optional[DeckSlotName]
707
+ if isinstance(load_location, str):
708
+ module_slot = deck_configuration_provider.get_deck_slot_for_cutout_id(
709
+ load_location
710
+ )
711
+ else:
712
+ module_slot = load_location
713
+ if module_slot == slot_name:
714
+ return self.get(module_id)
715
+
716
+ return None
717
+
718
+ def get_by_addressable_area(
719
+ self, addressable_area_name: str
720
+ ) -> Optional[LoadedModule]:
721
+ """Get the module associated with this addressable area, if any."""
722
+ for module_id in self._state.load_location_by_module_id.keys():
723
+ if addressable_area_name == self.get_provided_addressable_area(module_id):
724
+ return self.get(module_id)
725
+ return None
726
+
727
+ def _get_module_substate(
728
+ self, module_id: str, expected_type: Type[ModuleSubStateT], expected_name: str
729
+ ) -> ModuleSubStateT:
730
+ """Return the specific sub-state of a given module ID.
731
+
732
+ Args:
733
+ module_id: The ID of the module.
734
+ expected_type: The shape of the substate that we expect.
735
+ expected_name: A user-friendly name of the module to put into an
736
+ error message if the substate does not match the expected type.
737
+
738
+ Raises:
739
+ ModuleNotLoadedError: If module_id has not been loaded.
740
+ WrongModuleTypeError: If module_id has been loaded,
741
+ but it's not the expected type.
742
+ """
743
+ try:
744
+ substate = self._state.substate_by_module_id[module_id]
745
+ except KeyError as e:
746
+ raise errors.ModuleNotLoadedError(module_id=module_id) from e
747
+
748
+ if isinstance(substate, expected_type):
749
+ return substate
750
+
751
+ raise errors.WrongModuleTypeError(f"{module_id} is not a {expected_name}.")
752
+
753
+ def get_magnetic_module_substate(self, module_id: str) -> MagneticModuleSubState:
754
+ """Return a `MagneticModuleSubState` for the given Magnetic Module.
755
+
756
+ Raises:
757
+ ModuleNotLoadedError: If module_id has not been loaded.
758
+ WrongModuleTypeError: If module_id has been loaded,
759
+ but it's not a Magnetic Module.
760
+ """
761
+ return self._get_module_substate(
762
+ module_id=module_id,
763
+ expected_type=MagneticModuleSubState,
764
+ expected_name="Magnetic Module",
765
+ )
766
+
767
+ def get_heater_shaker_module_substate(
768
+ self, module_id: str
769
+ ) -> HeaterShakerModuleSubState:
770
+ """Return a `HeaterShakerModuleSubState` for the given Heater-Shaker Module.
771
+
772
+ Raises:
773
+ ModuleNotLoadedError: If module_id has not been loaded.
774
+ WrongModuleTypeError: If module_id has been loaded,
775
+ but it's not a Heater-Shaker Module.
776
+ """
777
+ return self._get_module_substate(
778
+ module_id=module_id,
779
+ expected_type=HeaterShakerModuleSubState,
780
+ expected_name="Heater-Shaker Module",
781
+ )
782
+
783
+ def get_temperature_module_substate(
784
+ self, module_id: str
785
+ ) -> TemperatureModuleSubState:
786
+ """Return a `TemperatureModuleSubState` for the given Temperature Module.
787
+
788
+ Raises:
789
+ ModuleNotLoadedError: If module_id has not been loaded.
790
+ WrongModuleTypeError: If module_id has been loaded,
791
+ but it's not a Temperature Module.
792
+ """
793
+ return self._get_module_substate(
794
+ module_id=module_id,
795
+ expected_type=TemperatureModuleSubState,
796
+ expected_name="Temperature Module",
797
+ )
798
+
799
+ def get_thermocycler_module_substate(
800
+ self, module_id: str
801
+ ) -> ThermocyclerModuleSubState:
802
+ """Return a `ThermocyclerModuleSubState` for the given Thermocycler Module.
803
+
804
+ Raises:
805
+ ModuleNotLoadedError: If module_id has not been loaded.
806
+ WrongModuleTypeError: If module_id has been loaded,
807
+ but it's not a Thermocycler Module.
808
+ """
809
+ return self._get_module_substate(
810
+ module_id=module_id,
811
+ expected_type=ThermocyclerModuleSubState,
812
+ expected_name="Thermocycler Module",
813
+ )
814
+
815
+ def get_absorbance_reader_substate(
816
+ self, module_id: str
817
+ ) -> AbsorbanceReaderSubState:
818
+ """Return a `AbsorbanceReaderSubState` for the given Absorbance Reader.
819
+
820
+ Raises:
821
+ ModuleNotLoadedError: If module_id has not been loaded.
822
+ WrongModuleTypeError: If module_id has been loaded,
823
+ but it's not an Absorbance Reader.
824
+ """
825
+ return self._get_module_substate(
826
+ module_id=module_id,
827
+ expected_type=AbsorbanceReaderSubState,
828
+ expected_name="Absorbance Reader",
829
+ )
830
+
831
+ def get_flex_stacker_substate(self, module_id: str) -> FlexStackerSubState:
832
+ """Return a `FlexStackerSubState` for the given Flex Stacker.
833
+
834
+ Raises:
835
+ ModuleNotLoadedError: If module_id has not been loaded.
836
+ WrongModuleTypeError: If module_id has been loaded,
837
+ but it's not a Flex Stacker.
838
+ """
839
+ return self._get_module_substate(
840
+ module_id=module_id,
841
+ expected_type=FlexStackerSubState,
842
+ expected_name="Flex Stacker",
843
+ )
844
+
845
+ def get_location(self, module_id: str) -> DeckSlotLocation:
846
+ """Get the slot location of the given module."""
847
+ location = self.get(module_id).location
848
+ if location is None:
849
+ raise errors.ModuleNotOnDeckError(
850
+ f"Module {module_id} is not loaded into a deck slot."
851
+ )
852
+ return location
853
+
854
+ def get_provided_addressable_area(self, module_id: str) -> str:
855
+ """Get the addressable area provided by this module.
856
+
857
+ If the current deck does not allow modules to provide locations (i.e., is an OT-2 deck)
858
+ then return the addressable area underneath the module.
859
+ """
860
+ module = self.get(module_id)
861
+
862
+ if isinstance(module.location, DeckSlotLocation):
863
+ location = module.location.slotName
864
+ elif module.model == ModuleModel.THERMOCYCLER_MODULE_V2:
865
+ location = DeckSlotName.SLOT_B1
866
+ else:
867
+ raise ValueError(
868
+ "Module location invalid for nominal module offset calculation."
869
+ )
870
+ if not self.get_deck_supports_module_fixtures():
871
+ return location.value
872
+ return self.ensure_and_convert_module_fixture_location(location, module.model)
873
+
874
+ def get_requested_model(self, module_id: str) -> Optional[ModuleModel]:
875
+ """Return the model by which this module was requested.
876
+
877
+ Or, if this module was not loaded with an explicit ``loadModule`` command,
878
+ return ``None``.
879
+
880
+ See also `get_connected_model()`.
881
+ """
882
+ try:
883
+ return self._state.requested_model_by_id[module_id]
884
+ except KeyError as e:
885
+ raise errors.ModuleNotLoadedError(module_id=module_id) from e
886
+
887
+ # TODO(jbl 2023-06-20) rename this method to better reflect it's not just "connected" modules
888
+ def get_connected_model(self, module_id: str) -> ModuleModel:
889
+ """Return the model of the connected module.
890
+
891
+ NOTE: This method will return the name for any module loaded, not just electronically connected ones.
892
+ This includes the Magnetic Block.
893
+
894
+ This can differ from `get_requested_model()` because of module compatibility.
895
+ For example, a ``loadModule`` command might request a ``temperatureModuleV1``
896
+ but return a ``temperatureModuleV2`` if that's what it finds actually connected
897
+ at run time.
898
+ """
899
+ return self.get(module_id).model
900
+
901
+ def get_serial_number(self, module_id: str) -> str:
902
+ """Get the hardware serial number of the given module.
903
+
904
+ If the underlying hardware API is simulating, this will be a dummy value
905
+ provided by the hardware API.
906
+ """
907
+ module = self.get(module_id)
908
+ if module.serialNumber is None:
909
+ raise ModuleNotConnectedError(
910
+ f"Expected a connected module and got a {module.model.name}"
911
+ )
912
+ return module.serialNumber
913
+
914
+ def get_definition(self, module_id: str) -> ModuleDefinition:
915
+ """Module definition by ID."""
916
+ try:
917
+ attached_module = self._state.hardware_by_module_id[module_id]
918
+ except KeyError as e:
919
+ raise errors.ModuleNotLoadedError(module_id=module_id) from e
920
+
921
+ return attached_module.definition
922
+
923
+ def get_dimensions(self, module_id: str) -> ModuleDimensions:
924
+ """Get the specified module's dimensions."""
925
+ return self.get_definition(module_id).dimensions
926
+
927
+ def get_nominal_offset_to_child(
928
+ self,
929
+ module_id: str,
930
+ # todo(mm, 2024-11-07): A method of one view taking a sibling view as an argument
931
+ # is unusual, and may be bug-prone if the order in which the views are updated
932
+ # matters. If we need to compute something that depends on module info and
933
+ # addressable area info, can we do that computation in GeometryView instead of
934
+ # here?
935
+ addressable_areas: AddressableAreaView,
936
+ ) -> Point:
937
+ """Get the nominal offset from a module's location to its child labware's location.
938
+
939
+ Includes the slot-specific transform. Does not include the child's
940
+ Labware Position Check offset.
941
+ """
942
+ base = self.get_nominal_offset_to_child_from_addressable_area(module_id)
943
+ if self.get_deck_supports_module_fixtures():
944
+ module_addressable_area = self.get_provided_addressable_area(module_id)
945
+ module_addressable_area_position = (
946
+ addressable_areas.get_addressable_area_offsets_from_cutout(
947
+ module_addressable_area
948
+ )
949
+ )
950
+ return base + module_addressable_area_position
951
+ else:
952
+ return base
953
+
954
+ def get_nominal_offset_to_child_from_addressable_area(
955
+ self, module_id: str
956
+ ) -> Point:
957
+ """Get the position offset for a child of this module from the nearest AA.
958
+
959
+ On the Flex, this is always (0, 0, 0); on the OT-2, since modules load on top
960
+ of addressable areas rather than providing addressable areas, the offset is
961
+ the labwareOffset from the module definition, rotated by the module's
962
+ slotTransform if appropriate.
963
+ """
964
+ if self.get_deck_supports_module_fixtures():
965
+ return Point(0, 0, 0)
966
+ else:
967
+ definition = self.get_definition(module_id)
968
+ slot = self.get_location(module_id).slotName.id
969
+
970
+ pre_transform: NDArray[npdouble] = array(
971
+ (
972
+ definition.labwareOffset.x,
973
+ definition.labwareOffset.y,
974
+ definition.labwareOffset.z,
975
+ 1,
976
+ )
977
+ )
978
+ xforms_ser = definition.slotTransforms.get(
979
+ str(self._state.deck_type.value), {}
980
+ ).get(
981
+ slot,
982
+ {
983
+ "labwareOffset": [
984
+ [1, 0, 0, 0],
985
+ [0, 1, 0, 0],
986
+ [0, 0, 1, 0],
987
+ [0, 0, 0, 1],
988
+ ]
989
+ },
990
+ )
991
+ xforms_ser_offset = xforms_ser["labwareOffset"]
992
+
993
+ # Apply the slot transform, if any
994
+ xform: NDArray[npdouble] = array(xforms_ser_offset)
995
+ xformed = dot(xform, pre_transform)
996
+ return Point(
997
+ x=xformed[0],
998
+ y=xformed[1],
999
+ z=xformed[2],
1000
+ )
1001
+
1002
+ def get_module_calibration_offset(
1003
+ self, module_id: str
1004
+ ) -> Optional[ModuleOffsetData]:
1005
+ """Get the calibration module offset."""
1006
+ module_serial = self.get(module_id).serialNumber
1007
+ if module_serial:
1008
+ return self._state.module_offset_by_serial.get(module_serial)
1009
+ return None
1010
+
1011
+ def get_overall_height(self, module_id: str) -> float:
1012
+ """Get the height of the module, excluding any labware loaded atop it."""
1013
+ return self.get_dimensions(module_id).bareOverallHeight
1014
+
1015
+ # TODO(mc, 2022-01-19): this method is missing unit test coverage
1016
+ def get_height_over_labware(self, module_id: str) -> float:
1017
+ """Get the height of module parts above module labware base."""
1018
+ return self.get_dimensions(module_id).overLabwareHeight
1019
+
1020
+ def get_module_highest_z(
1021
+ self, module_id: str, addressable_areas: AddressableAreaView
1022
+ ) -> float:
1023
+ """Get the highest z point of the module, as placed on the robot.
1024
+
1025
+ The highest Z of a module, unlike the bare overall height, depends on
1026
+ the robot it is on. We will calculate this value using the info we already have
1027
+ about the transformation of the module's placement, based on the deck it is on.
1028
+
1029
+ This value is calculated as:
1030
+ highest_z = ( nominal_robot_transformed_labware_offset_z
1031
+ + z_difference_between_default_labware_offset_point_and_overall_height
1032
+ + module_calibration_offset_z
1033
+ )
1034
+
1035
+ For OT2, the default_labware_offset point is the same as nominal_robot_transformed_labware_offset_z
1036
+ and hence the highest z will equal to the overall height of the module.
1037
+
1038
+ For Flex, since those two offsets are not the same, the final highest z will be
1039
+ transformed the same amount as the labware offset point is.
1040
+
1041
+ Note: For thermocycler, the lid height is not taken into account.
1042
+ """
1043
+ module_height = self.get_overall_height(module_id)
1044
+ default_lw_offset_point = self.get_definition(module_id).labwareOffset.z
1045
+ z_difference = module_height - default_lw_offset_point
1046
+
1047
+ nominal_transformed_lw_offset_z = self.get_nominal_offset_to_child(
1048
+ module_id=module_id, addressable_areas=addressable_areas
1049
+ ).z
1050
+ calibration_offset = self.get_module_calibration_offset(module_id)
1051
+ return (
1052
+ nominal_transformed_lw_offset_z
1053
+ + z_difference
1054
+ + (calibration_offset.moduleOffsetVector.z if calibration_offset else 0)
1055
+ )
1056
+
1057
+ # TODO(mc, 2022-01-19): this method is missing unit test coverage and
1058
+ # is also unused. Remove or add tests.
1059
+ def get_lid_height(self, module_id: str) -> float:
1060
+ """Get lid height if module is thermocycler."""
1061
+ definition = self.get_definition(module_id)
1062
+
1063
+ if (
1064
+ definition.moduleType == ModuleType.THERMOCYCLER
1065
+ and hasattr(definition.dimensions, "lidHeight")
1066
+ and definition.dimensions.lidHeight is not None
1067
+ ):
1068
+ return definition.dimensions.lidHeight
1069
+ else:
1070
+ raise errors.WrongModuleTypeError(
1071
+ f"Cannot get lid height of {definition.moduleType}"
1072
+ )
1073
+
1074
+ @staticmethod
1075
+ def get_magnet_home_to_base_offset(module_model: ModuleModel) -> float:
1076
+ """Return a Magnetic Module's home offset.
1077
+
1078
+ This is how far a Magnetic Module's magnets have to rise above their
1079
+ home position for their tops to be level with the bottom of the labware.
1080
+
1081
+ The offset is returned in true millimeters,
1082
+ even though GEN1 Magnetic Modules are sometimes controlled in units of
1083
+ half-millimeters ("short mm").
1084
+ """
1085
+ if module_model == ModuleModel.MAGNETIC_MODULE_V1:
1086
+ offset_in_half_mm = MAGNETIC_MODULE_OFFSET_TO_LABWARE_BOTTOM[
1087
+ "magneticModuleV1"
1088
+ ]
1089
+ return offset_in_half_mm / 2
1090
+ elif module_model == ModuleModel.MAGNETIC_MODULE_V2:
1091
+ return MAGNETIC_MODULE_OFFSET_TO_LABWARE_BOTTOM["magneticModuleV2"]
1092
+ else:
1093
+ raise errors.WrongModuleTypeError(
1094
+ f"Can't get magnet offset of {module_model}."
1095
+ )
1096
+
1097
+ @overload
1098
+ @classmethod
1099
+ def calculate_magnet_height(
1100
+ cls,
1101
+ *,
1102
+ module_model: ModuleModel,
1103
+ height_from_home: float,
1104
+ ) -> float:
1105
+ pass
1106
+
1107
+ @overload
1108
+ @classmethod
1109
+ def calculate_magnet_height(
1110
+ cls,
1111
+ *,
1112
+ module_model: ModuleModel,
1113
+ height_from_base: float,
1114
+ ) -> float:
1115
+ pass
1116
+
1117
+ @overload
1118
+ @classmethod
1119
+ def calculate_magnet_height(
1120
+ cls,
1121
+ *,
1122
+ module_model: ModuleModel,
1123
+ labware_default_height: float,
1124
+ offset_from_labware_default: float,
1125
+ ) -> float:
1126
+ pass
1127
+
1128
+ @classmethod
1129
+ def calculate_magnet_height(
1130
+ cls,
1131
+ *,
1132
+ module_model: ModuleModel,
1133
+ height_from_home: Optional[float] = None,
1134
+ height_from_base: Optional[float] = None,
1135
+ labware_default_height: Optional[float] = None,
1136
+ offset_from_labware_default: Optional[float] = None,
1137
+ ) -> float:
1138
+ """Normalize a Magnetic Module engage height to standard units.
1139
+
1140
+ Args:
1141
+ module_model: What kind of Magnetic Module to calculate the height for.
1142
+ height_from_home: A distance above the magnets' home position,
1143
+ in millimeters.
1144
+ height_from_base: A distance above the labware base plane,
1145
+ in millimeters.
1146
+ labware_default_height: A distance above the labware base plane,
1147
+ in millimeters, from a labware definition.
1148
+ offset_from_labware_default: A distance from the
1149
+ ``labware_default_height`` argument, in hardware units.
1150
+
1151
+ Negative values are allowed for all arguments, to move down instead of up.
1152
+
1153
+ See the overload signatures for which combinations of parameters are allowed.
1154
+
1155
+ Returns:
1156
+ The same height passed in, converted to be measured in
1157
+ millimeters above the module's labware base plane,
1158
+ suitable as input to a Magnetic Module engage Protocol Engine command.
1159
+ """
1160
+ if height_from_home is not None:
1161
+ home_to_base = cls.get_magnet_home_to_base_offset(module_model=module_model)
1162
+ return height_from_home - home_to_base
1163
+
1164
+ elif height_from_base is not None:
1165
+ return height_from_base
1166
+
1167
+ else:
1168
+ # Guaranteed statically by overload.
1169
+ assert labware_default_height is not None
1170
+ assert offset_from_labware_default is not None
1171
+ return labware_default_height + offset_from_labware_default
1172
+
1173
+ def should_dodge_thermocycler(
1174
+ self,
1175
+ from_slot: Union[DeckSlotName, StagingSlotName],
1176
+ to_slot: Union[DeckSlotName, StagingSlotName],
1177
+ ) -> bool:
1178
+ """Decide if the requested path would cross the thermocycler, if installed.
1179
+
1180
+ Returns True if we need to dodge, False otherwise.
1181
+ """
1182
+ all_mods = self.get_all()
1183
+ if any(ModuleModel.is_thermocycler_module_model(mod.model) for mod in all_mods):
1184
+ transit = (from_slot, to_slot)
1185
+ if transit in _THERMOCYCLER_SLOT_TRANSITS_TO_DODGE:
1186
+ return True
1187
+ return False
1188
+
1189
+ def is_edge_move_unsafe(self, mount: MountType, target_slot: DeckSlotName) -> bool:
1190
+ """Check if the slot next to target contains a module to be avoided, depending on mount."""
1191
+ slot_int = target_slot.as_int()
1192
+
1193
+ if mount is MountType.RIGHT:
1194
+ # Check left of the target
1195
+ neighbor_int = get_west_slot(slot_int)
1196
+ if neighbor_int is None:
1197
+ return False
1198
+ else:
1199
+ neighbor_slot = DeckSlotName.from_primitive(neighbor_int)
1200
+ else:
1201
+ # Check right of the target
1202
+ neighbor_int = get_east_slot(slot_int)
1203
+ if neighbor_int is None:
1204
+ return False
1205
+ else:
1206
+ neighbor_slot = DeckSlotName.from_primitive(neighbor_int)
1207
+
1208
+ # Convert the load location list from addressable areas and cutout IDs to a slot name list
1209
+ load_locations = self._state.load_location_by_module_id.values()
1210
+ module_slots = []
1211
+ for location in load_locations:
1212
+ if isinstance(location, str):
1213
+ module_slots.append(
1214
+ deck_configuration_provider.get_deck_slot_for_cutout_id(location)
1215
+ )
1216
+
1217
+ return neighbor_slot in module_slots
1218
+
1219
+ def select_hardware_module_to_load( # noqa: C901
1220
+ self,
1221
+ model: ModuleModel,
1222
+ location: str,
1223
+ attached_modules: Sequence[HardwareModule],
1224
+ expected_serial_number: Optional[str] = None,
1225
+ ) -> HardwareModule:
1226
+ """Get the next matching hardware module for the given model and location.
1227
+
1228
+ If a "matching" model is found already loaded in state at the requested
1229
+ location, that hardware module will be "reused" and selected. This behavior
1230
+ allows multiple load module commands to be issued while always preserving
1231
+ module hardware instance to deck slot mapping, which is required for
1232
+ multiples-of-a-module functionality.
1233
+
1234
+ Args:
1235
+ model: The requested module model. The selected module may have a
1236
+ different model if the definition lists the model as compatible.
1237
+ location: The location the module will be assigned to.
1238
+ attached_modules: All attached modules as reported by the HardwareAPI,
1239
+ in the order in which they should be used.
1240
+ expected_serial_number: An optional variable containing the serial number
1241
+ expected of the module identified.
1242
+
1243
+ Raises:
1244
+ ModuleNotAttachedError: A not-yet-assigned module matching the requested
1245
+ parameters could not be found in the attached modules list.
1246
+ ModuleAlreadyPresentError: A module of a different type is already
1247
+ assigned to the requested location.
1248
+ """
1249
+ existing_mod_in_slot = None
1250
+
1251
+ for (
1252
+ mod_id,
1253
+ load_location,
1254
+ ) in self._state.load_location_by_module_id.items():
1255
+ if isinstance(load_location, str) and location == load_location:
1256
+ existing_mod_in_slot = self._state.hardware_by_module_id.get(mod_id)
1257
+
1258
+ if existing_mod_in_slot:
1259
+ existing_def = existing_mod_in_slot.definition
1260
+
1261
+ if existing_def.model == model or model in existing_def.compatibleWith:
1262
+ return existing_mod_in_slot
1263
+
1264
+ else:
1265
+ _err = f" present in {location}"
1266
+ raise errors.ModuleAlreadyPresentError(
1267
+ f"A {existing_def.model.value} is already" + _err
1268
+ )
1269
+
1270
+ for m in attached_modules:
1271
+ if m not in self._state.hardware_by_module_id.values():
1272
+ if model == m.definition.model or model in m.definition.compatibleWith:
1273
+ if expected_serial_number is not None:
1274
+ if m.serial_number == expected_serial_number:
1275
+ return m
1276
+ else:
1277
+ return m
1278
+
1279
+ raise errors.ModuleNotAttachedError(
1280
+ f"No available {model.value} with {expected_serial_number or 'any'}"
1281
+ " serial found."
1282
+ )
1283
+
1284
+ def get_heater_shaker_movement_restrictors(
1285
+ self,
1286
+ ) -> List[HeaterShakerMovementRestrictors]:
1287
+ """Get shaking status, latch status, and location for every heater-shaker on deck."""
1288
+ hs_substates = [
1289
+ self.get_heater_shaker_module_substate(module_id=module.id)
1290
+ for module in self.get_all()
1291
+ if module.model == ModuleModel.HEATER_SHAKER_MODULE_V1
1292
+ ]
1293
+ hs_restrictors = [
1294
+ HeaterShakerMovementRestrictors(
1295
+ plate_shaking=substate.is_plate_shaking,
1296
+ latch_status=substate.labware_latch_status,
1297
+ deck_slot=self.get_location(substate.module_id).slotName.as_int(),
1298
+ )
1299
+ for substate in hs_substates
1300
+ ]
1301
+ return hs_restrictors
1302
+
1303
+ def raise_if_module_in_location(
1304
+ self,
1305
+ location: DeckSlotLocation,
1306
+ ) -> None:
1307
+ """Raise if the given location has a module in it."""
1308
+ for module in self.get_all():
1309
+ if module.model in _COLUMN_4_MODULES and module.location == location:
1310
+ raise errors.LocationIsOccupiedError(
1311
+ f"Module {module.model} is already present at {location.slotName.value[:1]}4."
1312
+ )
1313
+ if module.location == location:
1314
+ raise errors.LocationIsOccupiedError(
1315
+ f"Module {module.model} is already present at {location}."
1316
+ )
1317
+
1318
+ def get_default_gripper_offsets(
1319
+ self, module_id: str
1320
+ ) -> Optional[LabwareMovementOffsetData]:
1321
+ """Get the deck's default gripper offsets."""
1322
+ offsets = self.get_definition(module_id).gripperOffsets
1323
+ return offsets.get("default") if offsets else None
1324
+
1325
+ def get_overflowed_module_in_slot(
1326
+ self, slot_name: DeckSlotName
1327
+ ) -> Optional[LoadedModule]:
1328
+ """Get the module that's not loaded in the given slot, but still occupies the slot.
1329
+
1330
+ For example, if there's a thermocycler loaded in B1,
1331
+ `get_overflowed_module_in_slot(DeckSlotName.Slot_A1)` will return the loaded
1332
+ thermocycler module.
1333
+ """
1334
+ slots_by_id = self._state.additional_slots_occupied_by_module_id
1335
+
1336
+ for module_id, module_slots in slots_by_id.items():
1337
+ if module_slots and slot_name in module_slots:
1338
+ return self.get(module_id)
1339
+
1340
+ return None
1341
+
1342
+ def is_flex_deck_with_thermocycler(self) -> bool:
1343
+ """Return if this is a Flex deck with a thermocycler loaded in B1-A1 slots."""
1344
+ maybe_module = self.get_by_slot(
1345
+ DeckSlotName.SLOT_A1
1346
+ ) or self.get_overflowed_module_in_slot(DeckSlotName.SLOT_A1)
1347
+ if (
1348
+ self._state.deck_type == DeckType.OT3_STANDARD
1349
+ and maybe_module
1350
+ and maybe_module.model == ModuleModel.THERMOCYCLER_MODULE_V2
1351
+ ):
1352
+ return True
1353
+ else:
1354
+ return False
1355
+
1356
+ @staticmethod
1357
+ def convert_absorbance_reader_data_points(data: List[float]) -> Dict[str, float]:
1358
+ """Return the data from the Absorbance Reader module in a map of wells for each read value."""
1359
+ if len(data) == 96:
1360
+ # We have to reverse the reader values because the Opentrons Absorbance Reader is rotated 180 degrees on the deck
1361
+ raw_data = data.copy()
1362
+ raw_data.reverse()
1363
+ well_map: Dict[str, float] = {}
1364
+ for i, value in enumerate(raw_data):
1365
+ row = chr(ord("A") + i // 12) # Convert index to row (A-H)
1366
+ col = (i % 12) + 1 # Convert index to column (1-12)
1367
+ well_key = f"{row}{col}"
1368
+ # Truncate the value to the third decimal place
1369
+ well_map[well_key] = max(0.0, math.floor(value * 1000) / 1000)
1370
+ return well_map
1371
+ else:
1372
+ raise ValueError(
1373
+ "Only readings of 96 Well labware are supported for conversion to map of values by well."
1374
+ )
1375
+
1376
+ def get_deck_supports_module_fixtures(self) -> bool:
1377
+ """Check if the loaded deck supports modules as fixtures."""
1378
+ deck_type = self._state.deck_type
1379
+ return deck_type not in [DeckType.OT2_STANDARD, DeckType.OT2_SHORT_TRASH]
1380
+
1381
+ def ensure_and_convert_module_fixture_location(
1382
+ self,
1383
+ deck_slot: DeckSlotName,
1384
+ model: ModuleModel,
1385
+ ) -> str:
1386
+ """Ensure module fixture load location is valid.
1387
+
1388
+ Also, convert the deck slot to a valid module fixture addressable area.
1389
+ """
1390
+ deck_type = self._state.deck_type
1391
+
1392
+ if not self.get_deck_supports_module_fixtures():
1393
+ raise AreaNotInDeckConfigurationError(
1394
+ f"Invalid Deck Type: {deck_type.name} - Does not support modules as fixtures."
1395
+ )
1396
+
1397
+ assert deck_slot in DeckSlotName.ot3_slots()
1398
+ if model == ModuleModel.MAGNETIC_BLOCK_V1:
1399
+ return f"magneticBlockV1{deck_slot.value}"
1400
+
1401
+ elif model == ModuleModel.HEATER_SHAKER_MODULE_V1:
1402
+ # only allowed in column 1 & 3
1403
+ assert deck_slot.value[-1] in ("1", "3")
1404
+ return f"heaterShakerV1{deck_slot.value}"
1405
+
1406
+ elif model == ModuleModel.TEMPERATURE_MODULE_V2:
1407
+ # only allowed in column 1 & 3
1408
+ assert deck_slot.value[-1] in ("1", "3")
1409
+ return f"temperatureModuleV2{deck_slot.value}"
1410
+
1411
+ elif model == ModuleModel.THERMOCYCLER_MODULE_V2:
1412
+ return "thermocyclerModuleV2"
1413
+
1414
+ elif model == ModuleModel.ABSORBANCE_READER_V1:
1415
+ # only allowed in column 3
1416
+ assert deck_slot.value[-1] == "3"
1417
+ return f"absorbanceReaderV1{deck_slot.value}"
1418
+
1419
+ elif model == ModuleModel.FLEX_STACKER_MODULE_V1:
1420
+ # loaded to column 3 but the addressable area is in column 4
1421
+ assert deck_slot.value[-1] == "3"
1422
+ return f"flexStackerModuleV1{deck_slot.value[0]}4"
1423
+
1424
+ raise ValueError(
1425
+ f"Unknown module {model.name} has no addressable areas to provide."
1426
+ )
1427
+
1428
+ def absorbance_reader_dock_location(
1429
+ self, module_id: str
1430
+ ) -> AddressableAreaLocation:
1431
+ """Get the addressable area for the absorbance reader dock."""
1432
+ reader_slot = self.get_location(module_id)
1433
+ lid_doc_slot = get_adjacent_staging_slot(reader_slot.slotName)
1434
+ assert lid_doc_slot is not None
1435
+ lid_dock_area = AddressableAreaLocation(
1436
+ addressableAreaName="absorbanceReaderV1LidDock" + lid_doc_slot.value
1437
+ )
1438
+ return lid_dock_area
1439
+
1440
+ def get_stacker_max_fill_height(self, module_id: str) -> float:
1441
+ """Get the maximum fill height for the Flex Stacker."""
1442
+ definition = self.get_definition(module_id)
1443
+
1444
+ if (
1445
+ definition.moduleType == ModuleType.FLEX_STACKER
1446
+ and hasattr(definition.dimensions, "maxStackerFillHeight")
1447
+ and definition.dimensions.maxStackerFillHeight is not None
1448
+ ):
1449
+ return definition.dimensions.maxStackerFillHeight
1450
+ else:
1451
+ raise errors.WrongModuleTypeError(
1452
+ f"Cannot get max fill height of {definition.moduleType}"
1453
+ )
1454
+
1455
+ def stacker_max_pool_count_by_height(
1456
+ self,
1457
+ module_id: str,
1458
+ pool_height: float,
1459
+ pool_overlap: float,
1460
+ ) -> int:
1461
+ """Get the maximum stack count for the Flex Stacker by stack height."""
1462
+ max_fill_height = self.get_stacker_max_fill_height(module_id)
1463
+ assert max_fill_height > 0
1464
+ # Subtracting the pool overlap from the stack element (pool height) allows us to account for
1465
+ # elements nesting on one-another, and we must subtract from max height to apply starting offset.
1466
+ # Ex: Let H be the total height of the stack; h be the height of a stack element;
1467
+ # d be the stack overlap; and N be the number of labware. Then for N >= 1,
1468
+ # H = Nh - (N-1)d
1469
+ # H = Nh - Nd + d
1470
+ # H - d = N(h-d)
1471
+ # (H-d)/(h-d) = N
1472
+ return math.floor(
1473
+ (max_fill_height - pool_overlap) / (pool_height - pool_overlap)
1474
+ )
1475
+
1476
+ def stacker_contained_labware(
1477
+ self, module_id: str
1478
+ ) -> list[StackerStoredLabwareGroup]:
1479
+ """Get the labware contained in a Flex Stacker."""
1480
+ substate = self.get_flex_stacker_substate(module_id)
1481
+ return substate.get_contained_labware()
1482
+
1483
+ def stacker_max_pool_count(self, module_id: str) -> int | None:
1484
+ """Get the max stored labware in this stacker configuration."""
1485
+ substate = self.get_flex_stacker_substate(module_id)
1486
+ return substate.get_max_pool_count()
1487
+
1488
+ def validate_stacker_overlap_offset(
1489
+ self,
1490
+ module_id: str,
1491
+ overlap_offset: float,
1492
+ ) -> None:
1493
+ """The overlap offset provided should match the stacker configuration."""
1494
+ substate = self.get_flex_stacker_substate(module_id)
1495
+ configured = substate.get_pool_overlap()
1496
+ if not math.isclose(overlap_offset, configured, rel_tol=1e-9):
1497
+ raise ValueError(
1498
+ f"Provided overlap offset {overlap_offset} does not match "
1499
+ f"configured {configured}."
1500
+ )