opentrons 8.3.0a0__py2.py3-none-any.whl → 8.3.0a1__py2.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 (228) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  9. opentrons/drivers/asyncio/communication/errors.py +16 -3
  10. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  11. opentrons/drivers/command_builder.py +2 -2
  12. opentrons/drivers/flex_stacker/__init__.py +9 -0
  13. opentrons/drivers/flex_stacker/abstract.py +89 -0
  14. opentrons/drivers/flex_stacker/driver.py +260 -0
  15. opentrons/drivers/flex_stacker/simulator.py +109 -0
  16. opentrons/drivers/flex_stacker/types.py +138 -0
  17. opentrons/drivers/heater_shaker/driver.py +18 -3
  18. opentrons/drivers/temp_deck/driver.py +13 -3
  19. opentrons/drivers/thermocycler/driver.py +17 -3
  20. opentrons/execute.py +3 -1
  21. opentrons/hardware_control/__init__.py +1 -2
  22. opentrons/hardware_control/api.py +28 -20
  23. opentrons/hardware_control/backends/flex_protocol.py +4 -6
  24. opentrons/hardware_control/backends/ot3controller.py +177 -59
  25. opentrons/hardware_control/backends/ot3simulator.py +10 -8
  26. opentrons/hardware_control/backends/ot3utils.py +3 -13
  27. opentrons/hardware_control/dev_types.py +2 -0
  28. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  29. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  30. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  31. opentrons/hardware_control/emulation/settings.py +3 -4
  32. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  33. opentrons/hardware_control/instruments/ot2/pipette.py +9 -21
  34. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  35. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  36. opentrons/hardware_control/instruments/ot3/pipette.py +13 -22
  37. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  38. opentrons/hardware_control/modules/mod_abc.py +2 -2
  39. opentrons/hardware_control/motion_utilities.py +68 -0
  40. opentrons/hardware_control/nozzle_manager.py +39 -41
  41. opentrons/hardware_control/ot3_calibration.py +1 -1
  42. opentrons/hardware_control/ot3api.py +34 -22
  43. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  44. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  45. opentrons/hardware_control/protocols/liquid_handler.py +18 -0
  46. opentrons/hardware_control/protocols/motion_controller.py +6 -0
  47. opentrons/hardware_control/robot_calibration.py +1 -1
  48. opentrons/hardware_control/types.py +61 -0
  49. opentrons/protocol_api/__init__.py +20 -1
  50. opentrons/protocol_api/_liquid.py +24 -49
  51. opentrons/protocol_api/_liquid_properties.py +754 -0
  52. opentrons/protocol_api/_types.py +24 -0
  53. opentrons/protocol_api/core/common.py +2 -0
  54. opentrons/protocol_api/core/engine/instrument.py +67 -10
  55. opentrons/protocol_api/core/engine/labware.py +29 -7
  56. opentrons/protocol_api/core/engine/protocol.py +130 -5
  57. opentrons/protocol_api/core/engine/robot.py +139 -0
  58. opentrons/protocol_api/core/engine/well.py +4 -1
  59. opentrons/protocol_api/core/instrument.py +42 -4
  60. opentrons/protocol_api/core/labware.py +13 -4
  61. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +34 -3
  62. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  63. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  64. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  65. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +34 -3
  66. opentrons/protocol_api/core/protocol.py +34 -1
  67. opentrons/protocol_api/core/robot.py +51 -0
  68. opentrons/protocol_api/instrument_context.py +145 -43
  69. opentrons/protocol_api/labware.py +231 -7
  70. opentrons/protocol_api/module_contexts.py +21 -17
  71. opentrons/protocol_api/protocol_context.py +125 -4
  72. opentrons/protocol_api/robot_context.py +204 -32
  73. opentrons/protocol_api/validation.py +261 -3
  74. opentrons/protocol_engine/__init__.py +4 -0
  75. opentrons/protocol_engine/actions/actions.py +2 -3
  76. opentrons/protocol_engine/clients/sync_client.py +18 -0
  77. opentrons/protocol_engine/commands/__init__.py +81 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
  79. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
  80. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
  81. opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
  82. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  83. opentrons/protocol_engine/commands/aspirate.py +103 -53
  84. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  85. opentrons/protocol_engine/commands/blow_out.py +44 -39
  86. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  87. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  88. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  89. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  90. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  91. opentrons/protocol_engine/commands/command.py +73 -66
  92. opentrons/protocol_engine/commands/command_unions.py +101 -1
  93. opentrons/protocol_engine/commands/comment.py +1 -1
  94. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  95. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  96. opentrons/protocol_engine/commands/custom.py +6 -12
  97. opentrons/protocol_engine/commands/dispense.py +82 -48
  98. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  99. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  100. opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
  101. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  102. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  103. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  104. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  105. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  106. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  107. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  108. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  109. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  111. opentrons/protocol_engine/commands/home.py +13 -4
  112. opentrons/protocol_engine/commands/liquid_probe.py +60 -25
  113. opentrons/protocol_engine/commands/load_labware.py +29 -7
  114. opentrons/protocol_engine/commands/load_lid.py +146 -0
  115. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  116. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  117. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  118. opentrons/protocol_engine/commands/load_module.py +31 -10
  119. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  120. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  121. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  122. opentrons/protocol_engine/commands/move_labware.py +19 -6
  123. opentrons/protocol_engine/commands/move_relative.py +35 -25
  124. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  125. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  126. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  127. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  128. opentrons/protocol_engine/commands/movement_common.py +338 -0
  129. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  130. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  131. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  132. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  133. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  134. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  135. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  136. opentrons/protocol_engine/commands/robot/common.py +18 -0
  137. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  138. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  139. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  140. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  141. opentrons/protocol_engine/commands/save_position.py +14 -5
  142. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  143. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  144. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  145. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  146. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  147. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  148. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  149. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  150. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  151. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
  152. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  153. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  154. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  157. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  158. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
  159. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
  160. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
  161. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
  162. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  163. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  164. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  165. opentrons/protocol_engine/errors/__init__.py +8 -0
  166. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  167. opentrons/protocol_engine/errors/exceptions.py +50 -0
  168. opentrons/protocol_engine/execution/command_executor.py +1 -1
  169. opentrons/protocol_engine/execution/equipment.py +73 -5
  170. opentrons/protocol_engine/execution/gantry_mover.py +364 -8
  171. opentrons/protocol_engine/execution/movement.py +27 -0
  172. opentrons/protocol_engine/execution/pipetting.py +5 -1
  173. opentrons/protocol_engine/execution/tip_handler.py +4 -6
  174. opentrons/protocol_engine/notes/notes.py +1 -1
  175. opentrons/protocol_engine/protocol_engine.py +7 -6
  176. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  177. opentrons/protocol_engine/resources/labware_validation.py +5 -0
  178. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  179. opentrons/protocol_engine/resources/pipette_data_provider.py +12 -0
  180. opentrons/protocol_engine/slot_standardization.py +9 -9
  181. opentrons/protocol_engine/state/_move_types.py +9 -5
  182. opentrons/protocol_engine/state/_well_math.py +193 -0
  183. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  184. opentrons/protocol_engine/state/command_history.py +12 -0
  185. opentrons/protocol_engine/state/commands.py +17 -13
  186. opentrons/protocol_engine/state/files.py +10 -12
  187. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  188. opentrons/protocol_engine/state/frustum_helpers.py +57 -32
  189. opentrons/protocol_engine/state/geometry.py +47 -1
  190. opentrons/protocol_engine/state/labware.py +79 -25
  191. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  192. opentrons/protocol_engine/state/liquids.py +16 -4
  193. opentrons/protocol_engine/state/modules.py +52 -70
  194. opentrons/protocol_engine/state/motion.py +6 -1
  195. opentrons/protocol_engine/state/pipettes.py +135 -58
  196. opentrons/protocol_engine/state/state.py +21 -2
  197. opentrons/protocol_engine/state/state_summary.py +4 -2
  198. opentrons/protocol_engine/state/tips.py +11 -44
  199. opentrons/protocol_engine/state/update_types.py +343 -48
  200. opentrons/protocol_engine/state/wells.py +19 -11
  201. opentrons/protocol_engine/types.py +176 -28
  202. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  203. opentrons/protocol_reader/file_format_validator.py +5 -5
  204. opentrons/protocol_runner/json_file_reader.py +9 -3
  205. opentrons/protocol_runner/json_translator.py +51 -25
  206. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  207. opentrons/protocol_runner/protocol_runner.py +35 -4
  208. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  209. opentrons/protocol_runner/run_orchestrator.py +13 -3
  210. opentrons/protocols/advanced_control/common.py +38 -0
  211. opentrons/protocols/advanced_control/mix.py +1 -1
  212. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  213. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  214. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  215. opentrons/protocols/api_support/definitions.py +1 -1
  216. opentrons/protocols/api_support/instrument.py +1 -1
  217. opentrons/protocols/api_support/util.py +10 -0
  218. opentrons/protocols/labware.py +39 -6
  219. opentrons/protocols/models/json_protocol.py +5 -9
  220. opentrons/simulate.py +3 -1
  221. opentrons/types.py +162 -2
  222. opentrons/util/logging_config.py +1 -1
  223. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/METADATA +16 -15
  224. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/RECORD +228 -201
  225. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/WHEEL +1 -1
  226. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/LICENSE +0 -0
  227. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/entry_points.txt +0 -0
  228. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/top_level.txt +0 -0
@@ -53,7 +53,9 @@ class ModuleStatusServer(ProxyListener):
53
53
  self._connections[identifier] = connection
54
54
  for c in self._clients:
55
55
  c.write(
56
- Message(status="connected", connections=[connection]).json().encode()
56
+ Message(status="connected", connections=[connection])
57
+ .model_dump_json()
58
+ .encode()
57
59
  )
58
60
  c.write(b"\n")
59
61
 
@@ -72,7 +74,7 @@ class ModuleStatusServer(ProxyListener):
72
74
  for c in self._clients:
73
75
  c.write(
74
76
  Message(status="disconnected", connections=[connection])
75
- .json()
77
+ .model_dump_json()
76
78
  .encode()
77
79
  )
78
80
  c.write(MessageDelimiter)
@@ -95,7 +97,7 @@ class ModuleStatusServer(ProxyListener):
95
97
  # A client connected. Send a dump of all connected modules.
96
98
  m = Message(status="dump", connections=list(self._connections.values()))
97
99
 
98
- writer.write(m.json().encode())
100
+ writer.write(m.model_dump_json().encode())
99
101
  writer.write(MessageDelimiter)
100
102
 
101
103
  self._clients.add(writer)
@@ -1,7 +1,8 @@
1
1
  from typing import List
2
2
  from opentrons.hardware_control.emulation.types import ModuleType
3
3
  from opentrons.hardware_control.emulation.util import TEMPERATURE_ROOM
4
- from pydantic import BaseSettings, BaseModel
4
+ from pydantic import BaseModel
5
+ from pydantic_settings import BaseSettings, SettingsConfigDict
5
6
 
6
7
 
7
8
  class PipetteSettings(BaseModel):
@@ -113,8 +114,6 @@ class Settings(BaseSettings):
113
114
  emulator_port=9003, driver_port=9998
114
115
  )
115
116
  magdeck_proxy: ProxySettings = ProxySettings(emulator_port=9004, driver_port=9999)
116
-
117
- class Config:
118
- env_prefix = "OT_EMULATOR_"
117
+ model_config = SettingsConfigDict(env_prefix="OT_EMULATOR_")
119
118
 
120
119
  module_server: ModuleServerSettings = ModuleServerSettings()
@@ -123,7 +123,8 @@ def load_tip_length_for_pipette(
123
123
  ) -> TipLengthCalibration:
124
124
  if isinstance(tiprack, LabwareDefinition):
125
125
  tiprack = typing.cast(
126
- "TypeDictLabwareDef", tiprack.dict(exclude_none=True, exclude_unset=True)
126
+ "TypeDictLabwareDef",
127
+ tiprack.model_dump(exclude_none=True, exclude_unset=True),
127
128
  )
128
129
 
129
130
  tip_length_data = calibration_storage.load_tip_length_calibration(
@@ -28,7 +28,7 @@ from opentrons_shared_data.errors.exceptions import (
28
28
  CommandPreconditionViolated,
29
29
  )
30
30
  from opentrons_shared_data.pipette.ul_per_mm import (
31
- piecewise_volume_conversion,
31
+ calculate_ul_per_mm,
32
32
  PIPETTING_FUNCTION_FALLBACK_VERSION,
33
33
  PIPETTING_FUNCTION_LATEST_VERSION,
34
34
  )
@@ -97,7 +97,7 @@ class Pipette(AbstractInstrument[PipetteConfigurations]):
97
97
  use_old_aspiration_functions: bool = False,
98
98
  ) -> None:
99
99
  self._config = config
100
- self._config_as_dict = config.dict()
100
+ self._config_as_dict = config.model_dump()
101
101
  self._pipette_offset = pipette_offset_cal
102
102
  self._pipette_type = self._config.pipette_type
103
103
  self._pipette_version = self._config.version
@@ -277,7 +277,7 @@ class Pipette(AbstractInstrument[PipetteConfigurations]):
277
277
  self._config, elements, liquid_class
278
278
  )
279
279
  # Update the cached dict representation
280
- self._config_as_dict = self._config.dict()
280
+ self._config_as_dict = self._config.model_dump()
281
281
 
282
282
  def reload_configurations(self) -> None:
283
283
  self._config = load_pipette_data.load_definition(
@@ -286,7 +286,7 @@ class Pipette(AbstractInstrument[PipetteConfigurations]):
286
286
  self._pipette_model.pipette_version,
287
287
  self._pipette_model.oem_type,
288
288
  )
289
- self._config_as_dict = self._config.dict()
289
+ self._config_as_dict = self._config.model_dump()
290
290
 
291
291
  def reset_state(self) -> None:
292
292
  self._current_volume = 0.0
@@ -589,21 +589,9 @@ class Pipette(AbstractInstrument[PipetteConfigurations]):
589
589
  # want this to unbounded.
590
590
  @functools.lru_cache(maxsize=100)
591
591
  def ul_per_mm(self, ul: float, action: UlPerMmAction) -> float:
592
- if action == "aspirate":
593
- fallback = self._active_tip_settings.aspirate.default[
594
- PIPETTING_FUNCTION_FALLBACK_VERSION
595
- ]
596
- sequence = self._active_tip_settings.aspirate.default.get(
597
- self._pipetting_function_version, fallback
598
- )
599
- else:
600
- fallback = self._active_tip_settings.dispense.default[
601
- PIPETTING_FUNCTION_FALLBACK_VERSION
602
- ]
603
- sequence = self._active_tip_settings.dispense.default.get(
604
- self._pipetting_function_version, fallback
605
- )
606
- return piecewise_volume_conversion(ul, sequence)
592
+ return calculate_ul_per_mm(
593
+ ul, action, self._active_tip_settings, self._pipetting_function_version
594
+ )
607
595
 
608
596
  def __str__(self) -> str:
609
597
  return "{} current volume {}ul critical point: {} at {}".format(
@@ -673,8 +661,8 @@ def _reload_and_check_skip(
673
661
  # Same config, good enough
674
662
  return attached_instr, True
675
663
  else:
676
- newdict = new_config.dict()
677
- olddict = attached_instr.config.dict()
664
+ newdict = new_config.model_dump()
665
+ olddict = attached_instr.config.model_dump()
678
666
  changed: Set[str] = set()
679
667
  for k in newdict.keys():
680
668
  if newdict[k] != olddict[k]:
@@ -230,7 +230,7 @@ class PipetteHandlerProvider(Generic[MountType]):
230
230
  result["current_nozzle_map"] = instr.nozzle_manager.current_configuration
231
231
  result["min_volume"] = instr.liquid_class.min_volume
232
232
  result["max_volume"] = instr.liquid_class.max_volume
233
- result["channels"] = instr.channels
233
+ result["channels"] = instr._max_channels.value
234
234
  result["has_tip"] = instr.has_tip
235
235
  result["tip_length"] = instr.current_tip_length
236
236
  result["aspirate_speed"] = self.plunger_speed(
@@ -260,6 +260,13 @@ class PipetteHandlerProvider(Generic[MountType]):
260
260
  "pipette_bounding_box_offsets"
261
261
  ] = instr.config.pipette_bounding_box_offsets
262
262
  result["lld_settings"] = instr.config.lld_settings
263
+ result["plunger_positions"] = {
264
+ "top": instr.plunger_positions.top,
265
+ "bottom": instr.plunger_positions.bottom,
266
+ "blow_out": instr.plunger_positions.blow_out,
267
+ "drop_tip": instr.plunger_positions.drop_tip,
268
+ }
269
+ result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
263
270
  return cast(PipetteDict, result)
264
271
 
265
272
  @property
@@ -318,8 +318,8 @@ def _reload_gripper(
318
318
  # Same config, good enough
319
319
  return attached_instr, True
320
320
  else:
321
- newdict = new_config.dict()
322
- olddict = attached_instr.config.dict()
321
+ newdict = new_config.model_dump()
322
+ olddict = attached_instr.config.model_dump()
323
323
  changed: Set[str] = set()
324
324
  for k in newdict.keys():
325
325
  if newdict[k] != olddict[k]:
@@ -27,7 +27,7 @@ from opentrons_shared_data.errors.exceptions import (
27
27
  InvalidInstrumentData,
28
28
  )
29
29
  from opentrons_shared_data.pipette.ul_per_mm import (
30
- piecewise_volume_conversion,
30
+ calculate_ul_per_mm,
31
31
  PIPETTING_FUNCTION_FALLBACK_VERSION,
32
32
  PIPETTING_FUNCTION_LATEST_VERSION,
33
33
  )
@@ -80,7 +80,7 @@ class Pipette(AbstractInstrument[PipetteConfigurations]):
80
80
  use_old_aspiration_functions: bool = False,
81
81
  ) -> None:
82
82
  self._config = config
83
- self._config_as_dict = config.dict()
83
+ self._config_as_dict = config.model_dump()
84
84
  self._plunger_motor_current = config.plunger_motor_configurations
85
85
  self._pick_up_configurations = config.pick_up_tip_configurations
86
86
  self._plunger_homing_configurations = config.plunger_homing_configurations
@@ -257,7 +257,7 @@ class Pipette(AbstractInstrument[PipetteConfigurations]):
257
257
  self._pipette_model.pipette_version,
258
258
  self._pipette_model.oem_type,
259
259
  )
260
- self._config_as_dict = self._config.dict()
260
+ self._config_as_dict = self._config.model_dump()
261
261
 
262
262
  def reset_state(self) -> None:
263
263
  self._current_volume = 0.0
@@ -539,23 +539,13 @@ class Pipette(AbstractInstrument[PipetteConfigurations]):
539
539
  # want this to unbounded.
540
540
  @functools.lru_cache(maxsize=100)
541
541
  def ul_per_mm(self, ul: float, action: UlPerMmAction) -> float:
542
- if action == "aspirate":
543
- fallback = self._active_tip_settings.aspirate.default[
544
- PIPETTING_FUNCTION_FALLBACK_VERSION
545
- ]
546
- sequence = self._active_tip_settings.aspirate.default.get(
547
- self._pipetting_function_version, fallback
548
- )
549
- elif action == "blowout":
550
- return self._config.shaft_ul_per_mm
551
- else:
552
- fallback = self._active_tip_settings.dispense.default[
553
- PIPETTING_FUNCTION_FALLBACK_VERSION
554
- ]
555
- sequence = self._active_tip_settings.dispense.default.get(
556
- self._pipetting_function_version, fallback
557
- )
558
- return piecewise_volume_conversion(ul, sequence)
542
+ return calculate_ul_per_mm(
543
+ ul,
544
+ action,
545
+ self._active_tip_settings,
546
+ self._pipetting_function_version,
547
+ self._config.shaft_ul_per_mm,
548
+ )
559
549
 
560
550
  def __str__(self) -> str:
561
551
  return "{} current volume {}ul critical point: {} at {}".format(
@@ -595,6 +585,7 @@ class Pipette(AbstractInstrument[PipetteConfigurations]):
595
585
  "versioned_tip_overlap": self.tip_overlap,
596
586
  "back_compat_names": self._config.pipette_backcompat_names,
597
587
  "supported_tips": self.liquid_class.supported_tips,
588
+ "shaft_ul_per_mm": self._config.shaft_ul_per_mm,
598
589
  }
599
590
  )
600
591
  return self._config_as_dict
@@ -785,8 +776,8 @@ def _reload_and_check_skip(
785
776
  # Same config, good enough
786
777
  return attached_instr, True
787
778
  else:
788
- newdict = new_config.dict()
789
- olddict = attached_instr.config.dict()
779
+ newdict = new_config.model_dump()
780
+ olddict = attached_instr.config.model_dump()
790
781
  changed: Set[str] = set()
791
782
  for k in newdict.keys():
792
783
  if newdict[k] != olddict[k]:
@@ -237,6 +237,7 @@ class OT3PipetteHandler:
237
237
  "back_compat_names",
238
238
  "supported_tips",
239
239
  "lld_settings",
240
+ "available_sensors",
240
241
  ]
241
242
 
242
243
  instr_dict = instr.as_dict()
@@ -248,7 +249,7 @@ class OT3PipetteHandler:
248
249
  result["current_nozzle_map"] = instr.nozzle_manager.current_configuration
249
250
  result["min_volume"] = instr.liquid_class.min_volume
250
251
  result["max_volume"] = instr.liquid_class.max_volume
251
- result["channels"] = instr._max_channels
252
+ result["channels"] = instr._max_channels.value
252
253
  result["has_tip"] = instr.has_tip
253
254
  result["tip_length"] = instr.current_tip_length
254
255
  result["aspirate_speed"] = self.plunger_speed(
@@ -282,6 +283,14 @@ class OT3PipetteHandler:
282
283
  "pipette_bounding_box_offsets"
283
284
  ] = instr.config.pipette_bounding_box_offsets
284
285
  result["lld_settings"] = instr.config.lld_settings
286
+ result["plunger_positions"] = {
287
+ "top": instr.plunger_positions.top,
288
+ "bottom": instr.plunger_positions.bottom,
289
+ "blow_out": instr.plunger_positions.blow_out,
290
+ "drop_tip": instr.plunger_positions.drop_tip,
291
+ }
292
+ result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
293
+ result["available_sensors"] = instr.config.available_sensors
285
294
  return cast(PipetteDict, result)
286
295
 
287
296
  @property
@@ -2,7 +2,7 @@ import abc
2
2
  import asyncio
3
3
  import logging
4
4
  import re
5
- from typing import ClassVar, Mapping, Optional, TypeVar, cast
5
+ from typing import ClassVar, Mapping, Optional, TypeVar
6
6
  from packaging.version import InvalidVersion, parse, Version
7
7
  from opentrons.config import IS_ROBOT, ROBOT_FIRMWARE_DIR
8
8
  from opentrons.drivers.rpi_drivers.types import USBPort
@@ -31,7 +31,7 @@ def parse_fw_version(version: str) -> Version:
31
31
  raise InvalidVersion()
32
32
  except InvalidVersion:
33
33
  device_version = parse("v0.0.0")
34
- return cast(Version, device_version)
34
+ return device_version
35
35
 
36
36
 
37
37
  class AbstractModule(abc.ABC):
@@ -1,4 +1,6 @@
1
1
  """Utilities for calculating motion correctly."""
2
+ from logging import getLogger
3
+
2
4
  from functools import lru_cache
3
5
  from typing import Callable, Dict, Union, Optional, cast
4
6
  from collections import OrderedDict
@@ -11,6 +13,7 @@ from opentrons.util import linal
11
13
 
12
14
  from .types import Axis, OT3Mount
13
15
 
16
+ log = getLogger(__name__)
14
17
 
15
18
  # TODO: The offset_for_mount function should be defined with an overload
16
19
  # set, as with other functions in this module. Unfortunately, mypy < 0.920
@@ -36,6 +39,19 @@ from .types import Axis, OT3Mount
36
39
  # ) -> Point:
37
40
  # ...
38
41
 
42
+ EMPTY_ORDERED_DICT = OrderedDict(
43
+ (
44
+ (Axis.X, 0.0),
45
+ (Axis.Y, 0.0),
46
+ (Axis.Z_L, 0.0),
47
+ (Axis.Z_R, 0.0),
48
+ (Axis.Z_G, 0.0),
49
+ (Axis.P_L, 0.0),
50
+ (Axis.P_R, 0.0),
51
+ (Axis.Q, 0.0),
52
+ )
53
+ )
54
+
39
55
 
40
56
  @lru_cache(4)
41
57
  def offset_for_mount(
@@ -68,6 +84,7 @@ def target_position_from_absolute(
68
84
  )
69
85
  primary_cp = get_critical_point(mount)
70
86
  primary_z = Axis.by_mount(mount)
87
+
71
88
  target_position = OrderedDict(
72
89
  (
73
90
  (Axis.X, abs_position.x - offset.x - primary_cp.x),
@@ -97,6 +114,57 @@ def target_position_from_relative(
97
114
  return target_position
98
115
 
99
116
 
117
+ def target_axis_map_from_absolute(
118
+ primary_mount: Union[OT3Mount, Mount],
119
+ axis_map: Dict[Axis, float],
120
+ get_critical_point: Callable[[Union[Mount, OT3Mount]], Point],
121
+ left_mount_offset: Point,
122
+ right_mount_offset: Point,
123
+ gripper_mount_offset: Optional[Point] = None,
124
+ ) -> "OrderedDict[Axis, float]":
125
+ """Create an absolute target position for all specified machine axes."""
126
+ keys_for_target_position = list(axis_map.keys())
127
+
128
+ offset = offset_for_mount(
129
+ primary_mount, left_mount_offset, right_mount_offset, gripper_mount_offset
130
+ )
131
+ primary_cp = get_critical_point(primary_mount)
132
+ primary_z = Axis.by_mount(primary_mount)
133
+ target_position = OrderedDict()
134
+
135
+ if Axis.X in keys_for_target_position:
136
+ target_position[Axis.X] = axis_map[Axis.X] - offset.x - primary_cp.x
137
+ if Axis.Y in keys_for_target_position:
138
+ target_position[Axis.Y] = axis_map[Axis.Y] - offset.y - primary_cp.y
139
+ if primary_z in keys_for_target_position:
140
+ # Since this function is intended to be used in conjunction with `API.move_axes`
141
+ # we must leave out the carriage offset subtraction from the target position as
142
+ # `move_axes` already does this calculation.
143
+ target_position[primary_z] = axis_map[primary_z] - primary_cp.z
144
+
145
+ target_position.update(
146
+ {ax: val for ax, val in axis_map.items() if ax not in Axis.gantry_axes()}
147
+ )
148
+ return target_position
149
+
150
+
151
+ def target_axis_map_from_relative(
152
+ axis_map: Dict[Axis, float],
153
+ current_position: Dict[Axis, float],
154
+ ) -> "OrderedDict[Axis, float]":
155
+ """Create a target position for all specified machine axes."""
156
+ target_position = OrderedDict(
157
+ (
158
+ (ax, current_position[ax] + axis_map[ax])
159
+ for ax in EMPTY_ORDERED_DICT.keys()
160
+ if ax in axis_map.keys()
161
+ )
162
+ )
163
+ log.info(f"Current position {current_position} and axis map delta {axis_map}")
164
+ log.info(f"Relative move target {target_position}")
165
+ return target_position
166
+
167
+
100
168
  def target_position_from_plunger(
101
169
  mount: Union[Mount, OT3Mount],
102
170
  delta: float,
@@ -1,11 +1,13 @@
1
1
  from typing import Dict, List, Optional, Any, Sequence, Iterator, Tuple, cast
2
2
  from dataclasses import dataclass
3
3
  from collections import OrderedDict
4
- from enum import Enum
5
4
  from itertools import chain
6
5
 
7
6
  from opentrons.hardware_control.types import CriticalPoint
8
- from opentrons.types import Point
7
+ from opentrons.types import (
8
+ Point,
9
+ NozzleConfigurationType,
10
+ )
9
11
  from opentrons_shared_data.pipette.pipette_definition import (
10
12
  PipetteGeometryDefinition,
11
13
  PipetteRowDefinition,
@@ -41,43 +43,6 @@ def _row_col_indices_for_nozzle(
41
43
  )
42
44
 
43
45
 
44
- class NozzleConfigurationType(Enum):
45
- """
46
- Nozzle Configuration Type.
47
-
48
- Represents the current nozzle
49
- configuration stored in NozzleMap
50
- """
51
-
52
- COLUMN = "COLUMN"
53
- ROW = "ROW"
54
- SINGLE = "SINGLE"
55
- FULL = "FULL"
56
- SUBRECT = "SUBRECT"
57
-
58
- @classmethod
59
- def determine_nozzle_configuration(
60
- cls,
61
- physical_rows: "OrderedDict[str, List[str]]",
62
- current_rows: "OrderedDict[str, List[str]]",
63
- physical_cols: "OrderedDict[str, List[str]]",
64
- current_cols: "OrderedDict[str, List[str]]",
65
- ) -> "NozzleConfigurationType":
66
- """
67
- Determine the nozzle configuration based on the starting and
68
- ending nozzle.
69
- """
70
- if physical_rows == current_rows and physical_cols == current_cols:
71
- return NozzleConfigurationType.FULL
72
- if len(current_rows) == 1 and len(current_cols) == 1:
73
- return NozzleConfigurationType.SINGLE
74
- if len(current_rows) == 1:
75
- return NozzleConfigurationType.ROW
76
- if len(current_cols) == 1:
77
- return NozzleConfigurationType.COLUMN
78
- return NozzleConfigurationType.SUBRECT
79
-
80
-
81
46
  @dataclass
82
47
  class NozzleMap:
83
48
  """
@@ -113,6 +78,28 @@ class NozzleMap:
113
78
  full_instrument_rows: Dict[str, List[str]]
114
79
  #: A map of all the rows of an instrument
115
80
 
81
+ @classmethod
82
+ def determine_nozzle_configuration(
83
+ cls,
84
+ physical_rows: "OrderedDict[str, List[str]]",
85
+ current_rows: "OrderedDict[str, List[str]]",
86
+ physical_cols: "OrderedDict[str, List[str]]",
87
+ current_cols: "OrderedDict[str, List[str]]",
88
+ ) -> "NozzleConfigurationType":
89
+ """
90
+ Determine the nozzle configuration based on the starting and
91
+ ending nozzle.
92
+ """
93
+ if physical_rows == current_rows and physical_cols == current_cols:
94
+ return NozzleConfigurationType.FULL
95
+ if len(current_rows) == 1 and len(current_cols) == 1:
96
+ return NozzleConfigurationType.SINGLE
97
+ if len(current_rows) == 1:
98
+ return NozzleConfigurationType.ROW
99
+ if len(current_cols) == 1:
100
+ return NozzleConfigurationType.COLUMN
101
+ return NozzleConfigurationType.SUBRECT
102
+
116
103
  def __str__(self) -> str:
117
104
  return f"back_left_nozzle: {self.back_left} front_right_nozzle: {self.front_right} configuration: {self.configuration}"
118
105
 
@@ -216,6 +203,16 @@ class NozzleMap:
216
203
  """The total number of active nozzles in the configuration, and thus the number of tips that will be picked up."""
217
204
  return len(self.map_store)
218
205
 
206
+ @property
207
+ def physical_nozzle_count(self) -> int:
208
+ """The number of physical nozzles, regardless of configuration."""
209
+ return len(self.full_instrument_map_store)
210
+
211
+ @property
212
+ def active_nozzles(self) -> list[str]:
213
+ """An unstructured list of all nozzles active in the configuration."""
214
+ return list(self.map_store.keys())
215
+
219
216
  @classmethod
220
217
  def build( # noqa: C901
221
218
  cls,
@@ -274,7 +271,7 @@ class NozzleMap:
274
271
  )
275
272
 
276
273
  if (
277
- NozzleConfigurationType.determine_nozzle_configuration(
274
+ cls.determine_nozzle_configuration(
278
275
  physical_rows, rows, physical_columns, columns
279
276
  )
280
277
  != NozzleConfigurationType.FULL
@@ -289,6 +286,7 @@ class NozzleMap:
289
286
  if valid_nozzle_maps.maps[map_key] == list(map_store.keys()):
290
287
  validated_map_key = map_key
291
288
  break
289
+
292
290
  if validated_map_key is None:
293
291
  raise IncompatibleNozzleConfiguration(
294
292
  "Attempted Nozzle Configuration does not match any approved map layout for the current pipette."
@@ -302,7 +300,7 @@ class NozzleMap:
302
300
  full_instrument_map_store=physical_nozzles,
303
301
  full_instrument_rows=physical_rows,
304
302
  columns=columns,
305
- configuration=NozzleConfigurationType.determine_nozzle_configuration(
303
+ configuration=cls.determine_nozzle_configuration(
306
304
  physical_rows, rows, physical_columns, columns
307
305
  ),
308
306
  )
@@ -968,7 +968,7 @@ def load_attitude_matrix(to_default: bool = True) -> DeckCalibration:
968
968
  return DeckCalibration(
969
969
  attitude=apply_machine_transform(calibration_data.attitude),
970
970
  source=calibration_data.source,
971
- status=types.CalibrationStatus(**calibration_data.status.dict()),
971
+ status=types.CalibrationStatus(**calibration_data.status.model_dump()),
972
972
  belt_attitude=calibration_data.attitude,
973
973
  last_modified=calibration_data.lastModified,
974
974
  pipette_calibrated_with=calibration_data.pipetteCalibratedWith,