diff --git a/api/src/opentrons/protocol_engine/commands/aspirate.py b/api/src/opentrons/protocol_engine/commands/aspirate.py index 8e3dcde80eb..2ea8cd821b5 100644 --- a/api/src/opentrons/protocol_engine/commands/aspirate.py +++ b/api/src/opentrons/protocol_engine/commands/aspirate.py @@ -157,7 +157,7 @@ async def execute(self, params: AspirateParams) -> _ExecuteReturn: state_update=state_update, ) else: - state_update.set_operated_liquid( + state_update.set_liquid_operated( labware_id=labware_id, well_name=well_name, volume=-volume_aspirated, diff --git a/api/src/opentrons/protocol_engine/commands/dispense.py b/api/src/opentrons/protocol_engine/commands/dispense.py index 1285d159360..0386a450e95 100644 --- a/api/src/opentrons/protocol_engine/commands/dispense.py +++ b/api/src/opentrons/protocol_engine/commands/dispense.py @@ -124,7 +124,7 @@ async def execute(self, params: DispenseParams) -> _ExecuteReturn: state_update=state_update, ) else: - state_update.set_operated_liquid( + state_update.set_liquid_operated( labware_id=labware_id, well_name=well_name, volume=volume, diff --git a/api/src/opentrons/protocol_engine/commands/liquid_probe.py b/api/src/opentrons/protocol_engine/commands/liquid_probe.py index b58fea92219..8054bcc3e75 100644 --- a/api/src/opentrons/protocol_engine/commands/liquid_probe.py +++ b/api/src/opentrons/protocol_engine/commands/liquid_probe.py @@ -205,7 +205,7 @@ async def execute(self, params: _CommonParams) -> _LiquidProbeExecuteReturn: self._state_view, self._movement, self._pipetting, params ) if isinstance(z_pos_or_error, PipetteLiquidNotFoundError): - state_update.set_probed_liquid( + state_update.set_liquid_probed( labware_id=params.labwareId, well_name=params.wellName, height=None, @@ -232,7 +232,7 @@ async def execute(self, params: _CommonParams) -> _LiquidProbeExecuteReturn: well_name=params.wellName, height=z_pos_or_error, ) - state_update.set_probed_liquid( + state_update.set_liquid_probed( labware_id=params.labwareId, well_name=params.wellName, height=z_pos_or_error, @@ -286,7 +286,7 @@ async def execute(self, params: _CommonParams) -> _TryLiquidProbeExecuteReturn: labware_id=params.labwareId, well_name=params.wellName, height=z_pos ) - state_update.set_probed_liquid( + state_update.set_liquid_probed( labware_id=params.labwareId, well_name=params.wellName, height=z_pos, diff --git a/api/src/opentrons/protocol_engine/commands/load_liquid.py b/api/src/opentrons/protocol_engine/commands/load_liquid.py index d5fdfd13ed2..d37bc537b2d 100644 --- a/api/src/opentrons/protocol_engine/commands/load_liquid.py +++ b/api/src/opentrons/protocol_engine/commands/load_liquid.py @@ -61,7 +61,7 @@ async def execute( ) state_update = StateUpdate() - state_update.set_loaded_liquid( + state_update.set_liquid_loaded( labware_id=params.labwareId, volumes=params.volumeByWell, last_loaded=self._model_utils.get_timestamp(), diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py index 2e105f73ace..279fa7ec931 100644 --- a/api/src/opentrons/protocol_engine/state/update_types.py +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -177,7 +177,7 @@ class TipsUsedUpdate: @dataclasses.dataclass -class LoadLiquidUpdate: +class LiquidLoadedUpdate: """An update from loading a liquid.""" labware_id: str @@ -186,7 +186,7 @@ class LoadLiquidUpdate: @dataclasses.dataclass -class ProbeLiquidUpdate: +class LiquidProbedUpdate: """An update from probing a liquid.""" labware_id: str @@ -197,7 +197,7 @@ class ProbeLiquidUpdate: @dataclasses.dataclass -class OperateLiquidUpdate: +class LiquidOperatedUpdate: """An update from operating a liquid.""" labware_id: str @@ -225,11 +225,11 @@ class StateUpdate: tips_used: TipsUsedUpdate | NoChangeType = NO_CHANGE - loaded_liquid: LoadLiquidUpdate | NoChangeType = NO_CHANGE + liquid_loaded: LiquidLoadedUpdate | NoChangeType = NO_CHANGE - probed_liquid: ProbeLiquidUpdate | NoChangeType = NO_CHANGE + liquid_probed: LiquidProbedUpdate | NoChangeType = NO_CHANGE - operated_liquid: OperateLiquidUpdate | NoChangeType = NO_CHANGE + liquid_operated: LiquidOperatedUpdate | NoChangeType = NO_CHANGE # These convenience functions let the caller avoid the boilerplate of constructing a # complicated dataclass tree. @@ -367,20 +367,20 @@ def mark_tips_as_used( pipette_id=pipette_id, labware_id=labware_id, well_name=well_name ) - def set_loaded_liquid( + def set_liquid_loaded( self, labware_id: str, volumes: typing.Dict[str, float], last_loaded: datetime, ) -> None: """Add liquid volumes to well state. See `LoadLiquidUpdate`.""" - self.loaded_liquid = LoadLiquidUpdate( + self.liquid_loaded = LiquidLoadedUpdate( labware_id=labware_id, volumes=volumes, last_loaded=last_loaded, ) - def set_probed_liquid( + def set_liquid_probed( self, labware_id: str, well_name: str, @@ -389,7 +389,7 @@ def set_probed_liquid( volume: typing.Optional[float] = None, ) -> None: """Add a liquid height and volume to well state. See `ProbeLiquidUpdate`.""" - self.probed_liquid = ProbeLiquidUpdate( + self.liquid_probed = LiquidProbedUpdate( labware_id=labware_id, well_name=well_name, height=height, @@ -397,14 +397,14 @@ def set_probed_liquid( last_probed=last_probed, ) - def set_operated_liquid( + def set_liquid_operated( self, labware_id: str, well_name: str, volume: float, ) -> None: """Update liquid volumes in well state. See `OperateLiquidUpdate`.""" - self.operated_liquid = OperateLiquidUpdate( + self.liquid_operated = LiquidOperatedUpdate( labware_id=labware_id, well_name=well_name, volume=volume, diff --git a/api/src/opentrons/protocol_engine/state/wells.py b/api/src/opentrons/protocol_engine/state/wells.py index 0e8862b3872..e807d124b82 100644 --- a/api/src/opentrons/protocol_engine/state/wells.py +++ b/api/src/opentrons/protocol_engine/state/wells.py @@ -43,43 +43,43 @@ def handle_action(self, action: Action) -> None: def _handle_loaded_liquid_update( self, state_update: update_types.StateUpdate ) -> None: - if state_update.loaded_liquid != update_types.NO_CHANGE: - labware_id = state_update.loaded_liquid.labware_id + if state_update.liquid_loaded != update_types.NO_CHANGE: + labware_id = state_update.liquid_loaded.labware_id if labware_id not in self._state.loaded_volumes: self._state.loaded_volumes[labware_id] = {} - for (well, volume) in state_update.loaded_liquid.volumes.items(): + for (well, volume) in state_update.liquid_loaded.volumes.items(): self._state.loaded_volumes[labware_id][well] = LoadedVolumeInfo( volume=volume, - last_loaded=state_update.loaded_liquid.last_loaded, + last_loaded=state_update.liquid_loaded.last_loaded, operations_since_load=0, ) def _handle_probed_liquid_update( self, state_update: update_types.StateUpdate ) -> None: - if state_update.probed_liquid != update_types.NO_CHANGE: - labware_id = state_update.probed_liquid.labware_id - well_name = state_update.probed_liquid.well_name + if state_update.liquid_probed != update_types.NO_CHANGE: + labware_id = state_update.liquid_probed.labware_id + well_name = state_update.liquid_probed.well_name if labware_id not in self._state.probed_heights: self._state.probed_heights[labware_id] = {} if labware_id not in self._state.probed_volumes: self._state.probed_volumes[labware_id] = {} self._state.probed_heights[labware_id][well_name] = ProbedHeightInfo( - height=state_update.probed_liquid.height, - last_probed=state_update.probed_liquid.last_probed, + height=state_update.liquid_probed.height, + last_probed=state_update.liquid_probed.last_probed, ) self._state.probed_volumes[labware_id][well_name] = ProbedVolumeInfo( - volume=state_update.probed_liquid.volume, - last_probed=state_update.probed_liquid.last_probed, + volume=state_update.liquid_probed.volume, + last_probed=state_update.liquid_probed.last_probed, operations_since_probe=0, ) def _handle_operated_liquid_update( self, state_update: update_types.StateUpdate ) -> None: - if state_update.operated_liquid != update_types.NO_CHANGE: - labware_id = state_update.operated_liquid.labware_id - well_name = state_update.operated_liquid.well_name + if state_update.liquid_operated != update_types.NO_CHANGE: + labware_id = state_update.liquid_operated.labware_id + well_name = state_update.liquid_operated.well_name if ( labware_id in self._state.loaded_volumes and well_name in self._state.loaded_volumes[labware_id] @@ -88,7 +88,7 @@ def _handle_operated_liquid_update( assert prev_loaded_vol_info.volume is not None self._state.loaded_volumes[labware_id][well_name] = LoadedVolumeInfo( volume=prev_loaded_vol_info.volume - + state_update.operated_liquid.volume, + + state_update.liquid_operated.volume, last_loaded=prev_loaded_vol_info.last_loaded, operations_since_load=prev_loaded_vol_info.operations_since_load + 1, @@ -106,7 +106,7 @@ def _handle_operated_liquid_update( assert prev_probed_vol_info.volume is not None self._state.probed_volumes[labware_id][well_name] = ProbedVolumeInfo( volume=prev_probed_vol_info.volume - + state_update.operated_liquid.volume, + + state_update.liquid_operated.volume, last_probed=prev_probed_vol_info.last_probed, operations_since_probe=prev_probed_vol_info.operations_since_probe + 1, diff --git a/api/tests/opentrons/protocol_engine/commands/test_aspirate.py b/api/tests/opentrons/protocol_engine/commands/test_aspirate.py index 8d6f6d92179..5055d1ae185 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_aspirate.py +++ b/api/tests/opentrons/protocol_engine/commands/test_aspirate.py @@ -109,7 +109,12 @@ async def test_aspirate_implementation_no_prep( pipette_id="abc", new_location=update_types.Well(labware_id="123", well_name="A3"), new_deck_point=DeckPoint(x=1, y=2, z=3), - ) + ), + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id="123", + well_name="A3", + volume=-50, + ), ), ) @@ -178,7 +183,12 @@ async def test_aspirate_implementation_with_prep( pipette_id="abc", new_location=update_types.Well(labware_id="123", well_name="A3"), new_deck_point=DeckPoint(x=1, y=2, z=3), - ) + ), + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id="123", + well_name="A3", + volume=-50, + ), ), ) @@ -378,6 +388,11 @@ async def test_aspirate_implementation_meniscus( pipette_id="abc", new_location=update_types.Well(labware_id="123", well_name="A3"), new_deck_point=DeckPoint(x=1, y=2, z=3), - ) + ), + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id="123", + well_name="A3", + volume=-50, + ), ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_dispense.py b/api/tests/opentrons/protocol_engine/commands/test_dispense.py index 167223e6d9d..0a227d1958a 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_dispense.py +++ b/api/tests/opentrons/protocol_engine/commands/test_dispense.py @@ -92,6 +92,11 @@ async def test_dispense_implementation( ), new_deck_point=DeckPoint.construct(x=1, y=2, z=3), ), + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id="labware-id-abc123", + well_name="A3", + volume=42, + ), ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py index 6fb6ebc6935..fef29da18ce 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py +++ b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py @@ -104,6 +104,7 @@ async def test_liquid_probe_implementation( subject: EitherImplementation, params_type: EitherParamsType, result_type: EitherResultType, + model_utils: ModelUtils, ) -> None: """It should move to the destination and do a liquid probe there.""" location = WellLocation(origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1)) @@ -137,6 +138,17 @@ async def test_liquid_probe_implementation( ), ).then_return(15.0) + decoy.when( + state_view.geometry.get_well_volume_at_height( + labware_id="123", + well_name="A3", + height=15.0, + ), + ).then_return(30.0) + + timestamp = datetime(year=2020, month=1, day=2) + decoy.when(model_utils.get_timestamp()).then_return(timestamp) + result = await subject.execute(data) assert type(result.public) is result_type # Pydantic v1 only compares the fields. @@ -148,7 +160,14 @@ async def test_liquid_probe_implementation( pipette_id="abc", new_location=update_types.Well(labware_id="123", well_name="A3"), new_deck_point=DeckPoint(x=1, y=2, z=3), - ) + ), + liquid_probed=update_types.LiquidProbedUpdate( + labware_id="123", + well_name="A3", + height=15.0, + volume=30.0, + last_probed=timestamp, + ), ), ) @@ -212,7 +231,14 @@ async def test_liquid_not_found_error( pipette_id=pipette_id, new_location=update_types.Well(labware_id=labware_id, well_name=well_name), new_deck_point=DeckPoint(x=position.x, y=position.y, z=position.z), - ) + ), + liquid_probed=update_types.LiquidProbedUpdate( + labware_id=labware_id, + well_name=well_name, + height=None, + volume=None, + last_probed=error_timestamp, + ), ) if isinstance(subject, LiquidProbeImplementation): assert result == DefinedErrorData( diff --git a/api/tests/opentrons/protocol_engine/commands/test_load_liquid.py b/api/tests/opentrons/protocol_engine/commands/test_load_liquid.py index 5674dc057e8..d8641b1eb4b 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_load_liquid.py +++ b/api/tests/opentrons/protocol_engine/commands/test_load_liquid.py @@ -1,6 +1,7 @@ """Test load-liquid command.""" import pytest from decoy import Decoy +from datetime import datetime from opentrons.protocol_engine.commands.command import SuccessData from opentrons.protocol_engine.commands import ( @@ -10,6 +11,7 @@ ) from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.resources.model_utils import ModelUtils +from opentrons.protocol_engine.state import update_types @pytest.fixture @@ -30,6 +32,7 @@ async def test_load_liquid_implementation( decoy: Decoy, subject: LoadLiquidImplementation, mock_state_view: StateView, + model_utils: ModelUtils, ) -> None: """Test LoadLiquid command execution.""" data = LoadLiquidParams( @@ -37,9 +40,23 @@ async def test_load_liquid_implementation( liquidId="liquid-id", volumeByWell={"A1": 30, "B2": 100}, ) + + timestamp = datetime(year=2020, month=1, day=2) + decoy.when(model_utils.get_timestamp()).then_return(timestamp) + result = await subject.execute(data) - assert result == SuccessData(public=LoadLiquidResult(), private=None) + assert result == SuccessData( + public=LoadLiquidResult(), + private=None, + state_update=update_types.StateUpdate( + liquid_loaded=update_types.LiquidLoadedUpdate( + labware_id="labware-id", + volumes={"A1": 30, "B2": 100}, + last_loaded=timestamp, + ) + ), + ) decoy.verify(mock_state_view.liquid.validate_liquid_id("liquid-id"))