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

refactor(api,robot-server,app): use labwareURI instead of labwareID for placeLabwareState and unsafe/placeLabware command. #16719

Merged
merged 4 commits into from
Nov 7, 2024

Conversation

vegano1
Copy link
Contributor

@vegano1 vegano1 commented Nov 7, 2024

Overview

The placeLabwareState and unsafe/placeLabware commands rely on the labwareID to place labware held by the gripper back down to a deck slot. However, as correctly pointed out, this is wrong because the labwareID changes across runs. This was working for the plate reader lid because the lid is loaded with a fixed labwareID, however that would not be the case for other labware across different runs. This pull request replaces the labwareID with labwareURI used by the placeLabwareState and unsafe/placeLabware commands so the behavior is consistent across runs.

Closes: PLAT-580

Test Plan and Hands on Testing

  • Make sure we can run a plate reader protocol without issues
  • Make sure that the plate reader open/close_lid commands still work
  • Make sure the plate reader lid is returned to its dock (staging area) after the stop is physically and logically disengaged.
  • Make sure the run is canceled when the estop is pressed
  • Make sure the estop modal is shown while placing the plate reader lid back in its dock
  • Make sure that we only call placeLabware when the estop is pressed
  • Make sure all the above works on the ODD and Desktop app.
  • Make sure the plate reader does not get disconnected when the e-stop is pressed
  • Make sure the plate reader lid can still be moved when loading the module from the protocol setup.
from typing import cast
from opentrons import protocol_api
from opentrons.protocol_api.module_contexts import AbsorbanceReaderContext

# metadata
metadata = {
    'protocolName': 'Absorbance Reader Multi read test',
    'author': 'Platform Expansion',
}

requirements = {
    "robotType": "Flex",
    "apiLevel": "2.21",
}

# protocol run function
def run(protocol: protocol_api.ProtocolContext):
    mod = cast(AbsorbanceReaderContext, protocol.load_module("absorbanceReaderV1", "C3"))
    plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "C2")
    tiprack_1000 = protocol.load_labware(load_name='opentrons_flex_96_tiprack_1000ul', location="B2")
    trash_labware = protocol.load_trash_bin("B3")
    instrument = protocol.load_instrument("flex_8channel_1000", "right", tip_racks=[tiprack_1000])
    instrument.trash_container = trash_labware

    # pick up tip and perform action
    instrument.pick_up_tip(tiprack_1000.wells_by_name()['A1'])
    instrument.aspirate(100, plate.wells_by_name()['A1'])
    instrument.dispense(100, plate.wells_by_name()['B1'])
    instrument.return_tip()
   
    # Initialize to a single wavelength with reference wavelength
    # Issue: Make sure there is no labware here or youll get an error
    mod.close_lid()
    mod.initialize('single', [600], 450)

    # NOTE: CANNOT INITIALIZE WITH THE LID OPEN

    # Remove the Plate Reader lid using the Gripper.
    mod.open_lid()
    protocol.move_labware(plate, mod, use_gripper=True)
    mod.close_lid()

    # Take a reading and show the resulting absorbance values.
    # Issue: cant read before you initialize or you an get an error
    result = mod.read()
    msg = f"single: {result}"
    protocol.comment(msg=msg)
    protocol.pause(msg=msg)

    # Initialize to multiple wavelengths
    protocol.pause(msg="Perform Multi Read")
    mod.open_lid()
    protocol.move_labware(plate, "C2", use_gripper=True)
    mod.close_lid()
    mod.initialize('multi', [450, 570, 600])

    # Open the lid and move the labware into the reader
    mod.open_lid()
    protocol.move_labware(plate, mod, use_gripper=True)

    # pick up tip and perform action on labware inside plate reader
    instrument.pick_up_tip(tiprack_1000.wells_by_name()['A1'])
    instrument.aspirate(100, plate.wells_by_name()['A1'])
    instrument.dispense(100, plate.wells_by_name()['B1'])
    instrument.return_tip()

    mod.close_lid()

    # Take reading
    result = mod.read()
    msg = f"multi: {result}"
    protocol.comment(msg=msg)
    protocol.pause(msg=msg)

    # Place the Plate Reader lid back on using the Gripper.
    mod.open_lid()
    protocol.move_labware(plate, "C2", use_gripper=True)
    mod.close_lid() 

Changelog

  • use labwareURI instead of labwareID in the placeLabwareState object sent in the /runs/{runID}/currentState endpoint
  • use labwareURI instead of labwareID in the unsafe/placeLabware command
  • using the labwareURI in the unsafe/placeLabware command, load the labware directly
  • use labwareURI instead of labwareID in the usePlacePlateReaderLid.ts react hook

Review requests

  • manually loading in the labware is nasty work, any suggestions or advice are welcome
  • anything I might be overlooking?

Risk assessment

low, unreleased

…or placeLabwareState and command.

The labware_id is randomly generated when the labware is loaded, so it cannot be relied on across runs. This was working for the plate reader lid because the labwareID is fixed, however it would break with other labware
@vegano1 vegano1 requested review from a team as code owners November 7, 2024 15:29
@vegano1 vegano1 requested review from shlokamin, CaseyBatten and SyntaxColoring and removed request for a team November 7, 2024 15:29
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.

Awesome, thank you!

Comment on lines -35 to +41
labwareId: str = Field(..., description="The id of the labware to place.")
labwareURI: str = Field(..., description="Labware URI for labware.")
Copy link
Contributor

@SyntaxColoring SyntaxColoring Nov 7, 2024

Choose a reason for hiding this comment

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

loadLabware splits this out into separate loadName/namespace/version fields, so I might have matched that here for consistency.

Don't feel obligated to do that in this PR, though. If we care, we should be able to change this later because we only use this command in maintenance runs, so there are fewer persistence layer compatibility concerns.

Comment on lines +97 to +119
# TODO: We need a way to create temporary labware for moving around,
# the labware should get deleted once its used.
details = details_from_uri(params.labwareURI)
labware = await self._equipment.load_labware(
load_name=details.load_name,
namespace=details.namespace,
version=details.version,
location=location,
labware_id=None,
)

self._state_view.labware._state.definitions_by_uri[
params.labwareURI
] = labware.definition
self._state_view.labware._state.labware_by_id[
labware.labware_id
] = LoadedLabware.construct(
id=labware.labware_id,
location=location,
loadName=labware.definition.parameters.loadName,
definitionUri=params.labwareURI,
offsetId=labware.offsetId,
)
Copy link
Contributor

@SyntaxColoring SyntaxColoring Nov 7, 2024

Choose a reason for hiding this comment

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

For the benefit of other reviewers, and to answer your review question:

Modifying self._state_view.labware._state directly from a command implementation like this is, in most cases, not acceptable, as you called out. But we're going to replace this code imminently as part of RQA-3471, so this code makes sense to bridge the gap until then. I wouldn't sweat it, if it works.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree with that point, this is a temporary patch for the existing architecture which will be replaced with the changes Max mentioned.

Copy link

codecov bot commented Nov 7, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Please upload report for BASE (chore_release-8.2.0@0ad9ef8). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@                  Coverage Diff                   @@
##             chore_release-8.2.0   #16719   +/-   ##
======================================================
  Coverage                       ?   79.32%           
======================================================
  Files                          ?      120           
  Lines                          ?     4459           
  Branches                       ?        0           
======================================================
  Hits                           ?     3537           
  Misses                         ?      922           
  Partials                       ?        0           
Flag Coverage Δ
shared-data 74.02% <ø> (?)

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

Copy link
Contributor

@CaseyBatten CaseyBatten left a comment

Choose a reason for hiding this comment

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

Implementation looks solid overall, thanks for jumping on this one Brayan. A note below regarding some of the hard-coded approach being used down on the router end of things.

Comment on lines +97 to +119
# TODO: We need a way to create temporary labware for moving around,
# the labware should get deleted once its used.
details = details_from_uri(params.labwareURI)
labware = await self._equipment.load_labware(
load_name=details.load_name,
namespace=details.namespace,
version=details.version,
location=location,
labware_id=None,
)

self._state_view.labware._state.definitions_by_uri[
params.labwareURI
] = labware.definition
self._state_view.labware._state.labware_by_id[
labware.labware_id
] = LoadedLabware.construct(
id=labware.labware_id,
location=location,
loadName=labware.definition.parameters.loadName,
definitionUri=params.labwareURI,
offsetId=labware.offsetId,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Agree with that point, this is a temporary patch for the existing architecture which will be replaced with the changes Max mentioned.

@vegano1 vegano1 merged commit 347a23e into chore_release-8.2.0 Nov 7, 2024
38 checks passed
@vegano1 vegano1 deleted the PLAT-580-fix-place-labware branch November 7, 2024 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants