Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): implement pipetting restrictions around heater-shaker #11218

Merged
merged 16 commits into from
Jul 27, 2022

Conversation

sanni-t
Copy link
Member

@sanni-t sanni-t commented Jul 25, 2022

Overview

Closes #9915

Adds pipetting restrictions to PAPIv2 as specified in the ticket.

Changelog

  • Added flag_unsafe_move method to HeaterShakerContext and HeaterShakerGeometry
  • Moved all adjacent slot getter functions out of ../protocols/geometry/deck_conflict and into opentrons/utils since it's now being used by both PAPIv2 & PE. Tests for those functions upcoming.

Review requests

  • Question:
    • The method that checks for unsafe moves in the api is almost the same as that implemented in PE. Do we want to create a common, pure data 'restrictions checker' that can be used in both PAPI & PE to avoid having two sources of truth?
  • The use of Location vs LabwareLike vs Labware in the API can be confusing. Please make sure I have't mis-converted any types.

Risk assessment

Medium. Affects pipetting ability.

@sanni-t sanni-t requested a review from a team as a code owner July 25, 2022 21:24
@sanni-t sanni-t requested review from jbleon95 and mcous July 25, 2022 21:25
@sanni-t sanni-t added the robot-svcs Falls under the purview of the Robot Services squad (formerly CPX, Core Platform Experience). label Jul 25, 2022
@codecov
Copy link

codecov bot commented Jul 25, 2022

Codecov Report

Merging #11218 (93b2128) into edge (9774e64) will not change coverage.
The diff coverage is 90.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##             edge   #11218   +/-   ##
=======================================
  Coverage   73.81%   73.81%           
=======================================
  Files        2086     2086           
  Lines       57677    57677           
  Branches     5845     5845           
=======================================
  Hits        42574    42574           
  Misses      13826    13826           
  Partials     1277     1277           
Flag Coverage Δ
notify-server 89.17% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
api/src/opentrons/protocol_api/module_contexts.py 88.36% <ø> (ø)
...rc/opentrons/protocols/geometry/module_geometry.py 83.87% <87.50%> (ø)
...i/src/opentrons/protocol_api/instrument_context.py 88.66% <100.00%> (ø)

Copy link
Contributor

@mcous mcous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks mostly good, just a couple organizational thoughts. I'm good to 🚢 as longs as we've tested this on hardware

@@ -0,0 +1,47 @@
"""Getters for different types of adjacent slots."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually treat anything named util as a yellow flag. In my experience, you usually end up with a weird grab-bag of unrelated stuff that becomes difficult to find, test, and/or mock.

For example, looking at what's in opentrons.util right now, we have a four different things (plug an entirely empty file) that have nothing to do with each other:

  • Labware definition fetching plumbing
  • Datetime functions
  • Linear algebra functions
  • Logging config

Could we put these functions in opentrons.motion_planning instead? I think that's a way more descriptive home

return location - 1


def get_east_west_locations(location: int) -> List[int]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we're moving things around, location is a bit of an overloaded term for us. Should we shift this interface/language to something like get_east_west_slots(slot: int) -> List[int]?

@@ -156,6 +161,7 @@ def fake_plan_move(
# Once we have a location cache, that should be our from_loc
right.move_to(lw.wells()[1].top())
assert test_args[0].labware.as_well() == lw.wells()[0] # type: ignore[index]
print(f"#### Well Location= {lw.wells()[1].top()} ####")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove debugging code

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

woops

Comment on lines 1236 to 1239
# TODO (spp. 2022-07-20): use the ctx fixture once h/s ff is removed
ctx = papi.ProtocolContext(
implementation=ProtocolContextImplementation(sync_hardware=hardware.sync),
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? IIRC the flag doesn't have to be on when the context is created, just when the load_module is issued

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call

@sanni-t
Copy link
Member Author

sanni-t commented Jul 27, 2022

Tested this python protocol on a dev app and it raised errors correctly during analysis.

metadata = {
    'protocolName': 'PAPIv2 Pipetting restrictions checking protocol',
    'author': 'Opentrons <[email protected]>',
    'apiLevel': '2.12'
}

def run(context):
	# Load Heater-shaker module
	hs_mod = context.load_module("heaterShakerModuleV1", 4)

	# Load adapter + labware on module
	hs_plate = hs_mod.load_labware("opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat")

	# p300rack = [context.load_labware('opentrons_96_tiprack_300ul', 9)]
	p300rack = [context.load_labware('opentrons_96_tiprack_300ul', 1)]  # Multi-channel can access tipracks on NS of H/S

	p300_multi = context.load_instrument('p300_multi', 'right', tip_racks=p300rack)

	p10rack = [context.load_labware('opentrons_96_tiprack_10ul', 2)]
	p20 = context.load_instrument('p10_single', 'left', tip_racks= p10rack)

	# slot_1_lw = context.load_labware('agilent_1_reservoir_290ml', 1)
	slot_5_lw = context.load_labware('nest_96_wellplate_200ul_flat', 5)
	slot_7_lw = context.load_labware('nest_12_reservoir_15ml', 7)
	slot_8_lw = context.load_labware('biorad_96_wellplate_200ul_pcr', 8)
	slot_9_lw = context.load_labware('agilent_1_reservoir_290ml', 9)

	p20.pick_up_tip()
	p20.aspirate(10, slot_9_lw['A1'])
	
	p300_multi.pick_up_tip()
	p300_multi.aspirate(30, slot_9_lw['A1'])

	hs_mod.close_labware_latch()
	hs_mod.set_and_wait_for_shake_speed(rpm=500)	# Waits until H/S has reached 500rpm speed

	context.delay(minutes=1)
	
	########## Move to EWNS + h/s while shaking ##########

	# p20.dispense(10, hs_plate['A1'])	# Should raise error
	# p20.dispense(10, slot_1_lw['A1'])		# Should raise error
	# p20.dispense(10, slot_5_lw['A1'])		# Should raise error
	# p20.dispense(10, slot_7_lw['A1'])		# Should raise error
	p20.dispense(10, slot_8_lw['A1'])		# Should NOT raise error

	hs_mod.deactivate_shaker()

	########## Move to EW or h/s while latch open ##########

	hs_mod.open_labware_latch()

	# p20.dispense(10, hs_plate['A1'])	# Should raise error
	# p20.dispense(10, slot_5_lw['A1'])		# Should raise error
	p20.dispense(10, slot_8_lw['A1'])		# Should NOT raise error

	########## No multi-channel access to any labware on EW of H/S ##########
	
	hs_mod.close_labware_latch()

	p300_multi.dispense(30, hs_plate['A1'])		# Should NOT raise error
	# p300_multi.dispense(30, slot_5_lw['A1'])	# Should raise error
	p300_multi.dispense(30, slot_9_lw['A1'])	# Should NOT raise error

	########## Multichannels can access only tipracks on NS of H/S ##########
	
	# p300_multi.dispense(30, slot_7_lw['A1'])	# Should raise error


	p20.drop_tip()

	hs_mod.deactivate_shaker()
	hs_mod.deactivate_heater()

Copy link
Contributor

@SyntaxColoring SyntaxColoring left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Left some review suggestions.


Also:

The method that checks for unsafe moves in the api is almost the same as that implemented in PE. Do we want to create a common, pure data 'restrictions checker' that can be used in both PAPI & PE to avoid having two sources of truth?

I would definitely prefer that, but that doesn't have to happen here if you've already done things the other way.

Comment on lines +983 to +986
"""
Raise an error if attempting to perform a move that's deemed unsafe due to
the presence of the heater-shaker.
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not intended to be part of the public Python Protocol API, right?

Let's hide it from docs.opentrons.com:

Suggested change
"""
Raise an error if attempting to perform a move that's deemed unsafe due to
the presence of the heater-shaker.
"""
"""
Raise an error if attempting to perform a move that's deemed unsafe due to
the presence of the heater-shaker.
For Opentrons internal use only.
:meta private:
"""

"Cannot determine pipette movement safety."
)

cast(HeaterShakerGeometry, self.geometry).flag_unsafe_move(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can easily avoid this cast by changing this in the superclass:

def geometry(self) -> ModuleGeometry:

To this:

 def geometry(self) -> GeometryType: 

(GeometryType is a TypeVar defined in that file.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a TODO for this

api/src/opentrons/protocol_api/module_contexts.py Outdated Show resolved Hide resolved
api/src/opentrons/protocol_api/module_contexts.py Outdated Show resolved Hide resolved
Comment on lines 50 to 54
@pytest.fixture
def mock_heater_shaker_geometry() -> mock.MagicMock:
return mock.MagicMock()


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used for anything? Cmd+F isn't finding anything.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woops. Leftover from deleted code

@sanni-t sanni-t merged commit d9caa3d into edge Jul 27, 2022
@sanni-t sanni-t deleted the papiv2_hs_pipetting_restrictions branch July 27, 2022 22:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
robot-svcs Falls under the purview of the Robot Services squad (formerly CPX, Core Platform Experience).
Projects
None yet
Development

Successfully merging this pull request may close these issues.

h/s in PAPIv2: restrict pipetting actions around a heater-shaker
3 participants