Skip to content

Commit

Permalink
Merge branch 'edge' into EXEC-281-volume-tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
pmoegenburg committed Oct 24, 2024
2 parents c1c1fcf + df01e77 commit 517fa1a
Show file tree
Hide file tree
Showing 79 changed files with 1,302 additions and 549 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/app-test-build-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ jobs:
yarn config set cache-folder ${{ github.workspace }}/.yarn-cache
make setup-js
- name: 'test native(er) packages'
run: make test-js-internal tests="${{}matrix.shell}/src" cov_opts="--coverage=true"
run: make test-js-internal tests="${{matrix.shell}}/src" cov_opts="--coverage=true"
- name: 'Upload coverage report'
uses: 'codecov/codecov-action@v3'
with:
Expand Down
11 changes: 9 additions & 2 deletions api-client/src/runs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export interface GetRunsParams {
}

export interface Runs {
data: RunData[]
data: readonly RunData[]
links: RunsLinks
}

Expand All @@ -125,12 +125,15 @@ export const RUN_ACTION_TYPE_PAUSE: 'pause' = 'pause'
export const RUN_ACTION_TYPE_STOP: 'stop' = 'stop'
export const RUN_ACTION_TYPE_RESUME_FROM_RECOVERY: 'resume-from-recovery' =
'resume-from-recovery'
export const RUN_ACTION_TYPE_RESUME_FROM_RECOVERY_ASSUMING_FALSE_POSITIVE: 'resume-from-recovery-assuming-false-positive' =
'resume-from-recovery-assuming-false-positive'

export type RunActionType =
| typeof RUN_ACTION_TYPE_PLAY
| typeof RUN_ACTION_TYPE_PAUSE
| typeof RUN_ACTION_TYPE_STOP
| typeof RUN_ACTION_TYPE_RESUME_FROM_RECOVERY
| typeof RUN_ACTION_TYPE_RESUME_FROM_RECOVERY_ASSUMING_FALSE_POSITIVE

export interface RunAction {
id: string
Expand Down Expand Up @@ -172,7 +175,11 @@ export type RunError = RunCommandError
* Error Policy
*/

export type IfMatchType = 'ignoreAndContinue' | 'failRun' | 'waitForRecovery'
export type IfMatchType =
| 'assumeFalsePositiveAndContinue'
| 'ignoreAndContinue'
| 'failRun'
| 'waitForRecovery'

export interface ErrorRecoveryPolicy {
policyRules: Array<{
Expand Down
5 changes: 0 additions & 5 deletions api/src/opentrons/hardware_control/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1189,11 +1189,6 @@ async def tip_pickup_moves(

await self.retract(mount, spec.retract_target)

def cache_tip(self, mount: top_types.Mount, tip_length: float) -> None:
instrument = self.get_pipette(mount)
instrument.add_tip(tip_length=tip_length)
instrument.set_current_volume(0)

async def pick_up_tip(
self,
mount: top_types.Mount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,14 @@ def add_tip(self, mount: MountType, tip_length: float) -> None:
f"attach tip called while tip already attached to {instr}"
)

def cache_tip(self, mount: MountType, tip_length: float) -> None:
instrument = self.get_pipette(mount)
if instrument.has_tip:
# instrument.add_tip() would raise an AssertionError if we tried to overwrite an existing tip.
instrument.remove_tip()
instrument.add_tip(tip_length=tip_length)
instrument.set_current_volume(0)

def remove_tip(self, mount: MountType) -> None:
instr = self._attached_instruments[mount]
attached = self.attached_instruments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,14 @@ def add_tip(self, mount: OT3Mount, tip_length: float) -> None:
"attach tip called while tip already attached to {instr}"
)

def cache_tip(self, mount: OT3Mount, tip_length: float) -> None:
instrument = self.get_pipette(mount)
if instrument.has_tip:
# instrument.add_tip() would raise an AssertionError if we tried to overwrite an existing tip.
instrument.remove_tip()
instrument.add_tip(tip_length=tip_length)
instrument.set_current_volume(0)

def remove_tip(self, mount: OT3Mount) -> None:
instr = self._attached_instruments[mount]
attached = self.attached_instruments
Expand Down
14 changes: 5 additions & 9 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2236,15 +2236,6 @@ async def _tip_motor_action(
)
await self.home_gear_motors()

def cache_tip(
self, mount: Union[top_types.Mount, OT3Mount], tip_length: float
) -> None:
realmount = OT3Mount.from_mount(mount)
instrument = self._pipette_handler.get_pipette(realmount)

instrument.add_tip(tip_length=tip_length)
instrument.set_current_volume(0)

async def pick_up_tip(
self,
mount: Union[top_types.Mount, OT3Mount],
Expand Down Expand Up @@ -2613,6 +2604,11 @@ def add_tip(
) -> None:
self._pipette_handler.add_tip(OT3Mount.from_mount(mount), tip_length)

def cache_tip(
self, mount: Union[top_types.Mount, OT3Mount], tip_length: float
) -> None:
self._pipette_handler.cache_tip(OT3Mount.from_mount(mount), tip_length)

def remove_tip(self, mount: Union[top_types.Mount, OT3Mount]) -> None:
self._pipette_handler.remove_tip(OT3Mount.from_mount(mount))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,17 +142,24 @@ def get_instrument_max_height(
"""
...

# todo(mm, 2024-10-17): Consider deleting this in favor of cache_tip(), which is
# the same except for `assert`s, if we can do so without breaking anything.
# todo(mm, 2024-10-17): Consider deleting this in favor of cache_tip()
# if we can do so without breaking anything.
def add_tip(self, mount: MountArgType, tip_length: float) -> None:
"""Inform the hardware that a tip is now attached to a pipette.
If a tip is already attached, this no-ops.
This changes the critical point of the pipette to make sure that
the end of the tip is what moves around, and allows liquid handling.
"""
...

def cache_tip(self, mount: MountArgType, tip_length: float) -> None:
"""Inform the hardware that a tip is now attached to a pipette.
This is like `add_tip()`, except that if a tip is already attached,
this replaces it instead of no-opping.
"""
...

def remove_tip(self, mount: MountArgType) -> None:
Expand Down
4 changes: 4 additions & 0 deletions api/src/opentrons/protocol_engine/commands/move_labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ async def execute(self, params: MoveLabwareParams) -> _ExecuteReturn: # noqa: C
top_labware_definition=current_labware_definition,
bottom_labware_id=available_new_location.labwareId,
)
if params.labwareId == available_new_location.labwareId:
raise LabwareMovementNotAllowedError(
"Cannot move a labware onto itself."
)

# Allow propagation of ModuleNotLoadedError.
new_offset_id = self._equipment.find_applicable_labware_offset_id(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ async def _drop_tip(self) -> None:
try:
if self._state_store.labware.get_fixed_trash_id() == FIXED_TRASH_ID:
# OT-2 and Flex 2.15 protocols will default to the Fixed Trash Labware
await self._tip_handler.add_tip(pipette_id=pipette_id, tip=tip)
await self._tip_handler.cache_tip(
pipette_id=pipette_id, tip=tip
)
await self._movement_handler.move_to_well(
pipette_id=pipette_id,
labware_id=FIXED_TRASH_ID,
Expand All @@ -90,7 +92,9 @@ async def _drop_tip(self) -> None:
)
elif self._state_store.config.robot_type == "OT-2 Standard":
# API 2.16 and above OT2 protocols use addressable areas
await self._tip_handler.add_tip(pipette_id=pipette_id, tip=tip)
await self._tip_handler.cache_tip(
pipette_id=pipette_id, tip=tip
)
await self._movement_handler.move_to_addressable_area(
pipette_id=pipette_id,
addressable_area_name="fixedTrash",
Expand Down
34 changes: 13 additions & 21 deletions api/src/opentrons/protocol_engine/execution/tip_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None:
TipAttachedError
"""

async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None:
async def cache_tip(self, pipette_id: str, tip: TipGeometry) -> None:
"""Tell the Hardware API that a tip is attached."""

async def get_tip_presence(self, pipette_id: str) -> TipPresenceStatus:
Expand Down Expand Up @@ -234,31 +234,23 @@ async def pick_up_tip(
labware_definition=self._state_view.labware.get_definition(labware_id),
nominal_fallback=nominal_tip_geometry.length,
)
tip_geometry = TipGeometry(
length=actual_tip_length,
diameter=nominal_tip_geometry.diameter,
volume=nominal_tip_geometry.volume,
)

await self._hardware_api.tip_pickup_moves(
mount=hw_mount, presses=None, increment=None
)
# Allow TipNotAttachedError to propagate.
await self.verify_tip_presence(pipette_id, TipPresenceStatus.PRESENT)

self._hardware_api.cache_tip(hw_mount, actual_tip_length)
await self._hardware_api.prepare_for_aspirate(hw_mount)

self._hardware_api.set_current_tiprack_diameter(
mount=hw_mount,
tiprack_diameter=nominal_tip_geometry.diameter,
)
await self.cache_tip(pipette_id, tip_geometry)

self._hardware_api.set_working_volume(
mount=hw_mount,
tip_volume=nominal_tip_geometry.volume,
)
await self._hardware_api.prepare_for_aspirate(hw_mount)

return TipGeometry(
length=actual_tip_length,
diameter=nominal_tip_geometry.diameter,
volume=nominal_tip_geometry.volume,
)
return tip_geometry

async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None:
"""See documentation on abstract base class."""
Expand All @@ -279,11 +271,11 @@ async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None:
self._hardware_api.remove_tip(hw_mount)
self._hardware_api.set_current_tiprack_diameter(hw_mount, 0)

async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None:
async def cache_tip(self, pipette_id: str, tip: TipGeometry) -> None:
"""See documentation on abstract base class."""
hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()

self._hardware_api.add_tip(mount=hw_mount, tip_length=tip.length)
self._hardware_api.cache_tip(mount=hw_mount, tip_length=tip.length)

self._hardware_api.set_current_tiprack_diameter(
mount=hw_mount,
Expand Down Expand Up @@ -422,12 +414,12 @@ async def drop_tip(
expected_has_tip=True,
)

async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None:
async def cache_tip(self, pipette_id: str, tip: TipGeometry) -> None:
"""Add a tip using a virtual pipette.
This should not be called when using virtual pipettes.
"""
assert False, "TipHandler.add_tip should not be used with virtual pipettes"
assert False, "TipHandler.cache_tip should not be used with virtual pipettes"

async def verify_tip_presence(
self,
Expand Down
6 changes: 5 additions & 1 deletion api/src/opentrons/util/logging_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

from opentrons.config import CONFIG, ARCHITECTURE, SystemArchitecture

from opentrons_hardware.sensors import SENSOR_LOG_NAME
if ARCHITECTURE is SystemArchitecture.BUILDROOT:
from opentrons_hardware.sensors import SENSOR_LOG_NAME
else:
# we don't use the sensor log on ot2 or host
SENSOR_LOG_NAME = "unused"


def _host_config(level_value: int) -> Dict[str, Any]:
Expand Down
35 changes: 35 additions & 0 deletions api/tests/opentrons/protocol_engine/commands/test_move_labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -803,3 +803,38 @@ async def test_move_labware_raises_when_moving_fixed_trash_labware(
match="Cannot move fixed trash labware 'My cool labware'.",
):
await subject.execute(data)


async def test_labware_raises_when_moved_onto_itself(
decoy: Decoy,
subject: MoveLabwareImplementation,
state_view: StateView,
) -> None:
"""It should raise when the OnLabwareLocation has the same labware ID as the labware being moved."""
data = MoveLabwareParams(
labwareId="the-same-labware-id",
newLocation=OnLabwareLocation(labwareId="a-cool-labware-id"),
strategy=LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE,
)

decoy.when(state_view.labware.get(labware_id="the-same-labware-id")).then_return(
LoadedLabware(
id="the-same-labware-id",
loadName="load-name",
definitionUri="opentrons-test/load-name/1",
location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4),
offsetId=None,
)
)

decoy.when(
state_view.geometry.ensure_location_not_occupied(
location=OnLabwareLocation(labwareId="a-cool-labware-id"),
)
).then_return(OnLabwareLocation(labwareId="the-same-labware-id"))

with pytest.raises(
errors.LabwareMovementNotAllowedError,
match="Cannot move a labware onto itself.",
):
await subject.execute(data)
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ async def test_hardware_stopping_sequence_no_tip_drop(
decoy.verify(await hardware_api.stop(home_after=False), times=1)

decoy.verify(
await mock_tip_handler.add_tip(
await mock_tip_handler.cache_tip(
pipette_id="pipette-id",
tip=TipGeometry(length=1.0, volume=2.0, diameter=3.0),
),
Expand All @@ -181,7 +181,7 @@ async def test_hardware_stopping_sequence_no_pipette(
)

decoy.when(
await mock_tip_handler.add_tip(
await mock_tip_handler.cache_tip(
pipette_id="pipette-id",
tip=TipGeometry(length=1.0, volume=2.0, diameter=3.0),
),
Expand Down Expand Up @@ -271,7 +271,7 @@ async def test_hardware_stopping_sequence_with_fixed_trash(
await movement.home(
axes=[MotorAxis.X, MotorAxis.Y, MotorAxis.LEFT_Z, MotorAxis.RIGHT_Z]
),
await mock_tip_handler.add_tip(
await mock_tip_handler.cache_tip(
pipette_id="pipette-id",
tip=TipGeometry(length=1.0, volume=2.0, diameter=3.0),
),
Expand Down Expand Up @@ -320,7 +320,7 @@ async def test_hardware_stopping_sequence_with_OT2_addressable_area(
await movement.home(
axes=[MotorAxis.X, MotorAxis.Y, MotorAxis.LEFT_Z, MotorAxis.RIGHT_Z]
),
await mock_tip_handler.add_tip(
await mock_tip_handler.cache_tip(
pipette_id="pipette-id",
tip=TipGeometry(length=1.0, volume=2.0, diameter=3.0),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,10 @@ async def test_add_tip(
MountType.LEFT
)

await subject.add_tip(pipette_id="pipette-id", tip=tip)
await subject.cache_tip(pipette_id="pipette-id", tip=tip)

decoy.verify(
mock_hardware_api.add_tip(mount=Mount.LEFT, tip_length=50),
mock_hardware_api.cache_tip(mount=Mount.LEFT, tip_length=50),
mock_hardware_api.set_current_tiprack_diameter(
mount=Mount.LEFT,
tiprack_diameter=5,
Expand Down
Loading

0 comments on commit 517fa1a

Please sign in to comment.