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): add getNexTip protocol engine command #17038

Merged
merged 7 commits into from
Dec 9, 2024
Merged

Conversation

jbleon95
Copy link
Contributor

@jbleon95 jbleon95 commented Dec 4, 2024

Overview

Addresses part of AUTH-1063.

This PR adds a new getNextTip protocol engine command, for use in getting next tip information directly from the engine. The command takes in 3 parameters: a pipette ID, a list of labware ID (corresponding to which tip racks to find the next available tip in), and optionally a starting tip in the form of a well name (if not included this automatically resolves to "A1", mirroring how the API version of tip tracking works. It's result returns either a new nextTipInfo object containing the labware ID and well name of the next available tip (or tip to target for non-single channel/single tip configuration pipettes), or None if no available tip can be found for that pipette and tip rack(s) combination.

Test Plan and Hands on Testing

Tested on robot with Postman, along with unit test coverage.

Changelog

  • added getNextTip protocol engine command to return information about next available tip

Risk assessment

Low, new command that does not affect state.

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.

Thanks! Code all looks good to me, but I guess I have some fundamental questions.

api/src/opentrons/protocol_engine/types.py Show resolved Hide resolved
else:
next_tip = None

return SuccessData(public=GetNextTipResult(nextTipInfo=next_tip))
Copy link
Contributor

@SyntaxColoring SyntaxColoring Dec 4, 2024

Choose a reason for hiding this comment

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

This looks like it'll work as described, but if this command isn't actually doing anything—not affecting state nor making the robot move—I guess it doesn't seem like we should be making it a command?

Like, PAPI can get this functionality by calling state_view.tips.get_next_tip(). Or some higher-level function built around that, if it's not sufficient on its own. And the HTTP API can expose this through some non-command endpoint, probably /runs/{runId}/currentState.

Reasons to avoid making it a command include avoiding database compatibility concerns and avoiding increasing our public API surface area. Especially given weirdness about startingWellName, mentioned below.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, ya, that is a good point.

Copy link
Member

Choose a reason for hiding this comment

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

I get what you're saying, but the thing is that we don't have other well-organized means to do a "structured query", I guess you'd say. It's a component of a pickUpNextTip command, or really pickUpNextTip concept, that will need to be in transfers.

We absolutely could implement it as a public method of the engine, or of an engine state view, and plumb it up through the runner and the orchestrator and the run store and the run to an HTTP route, but that increases our public API surface area also. And this way you get to see it in the runlog.

Copy link
Member

@sanni-t sanni-t Dec 4, 2024

Choose a reason for hiding this comment

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

I guess I want to pass the question back at you @SyntaxColoring as to whether error recovery or any other EXEC work will need a client to fetch the next tip from engine. We are planning on adding a pickUpNextTip command sometime later but the critical part for us is knowing which tip to pick up from, and as you correctly gathered, we will be fine using a state getter function for this since it's an internal code that'll need this info. But not sure if a client will need to fetch this info too. Since auto-tip tracking in engine is a feature that is needed by a few projects, a command seemed more useful but maybe we don't need both getNextTip and pickUpNextTip.

Copy link
Contributor

Choose a reason for hiding this comment

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

I get what you're saying, but the thing is that we don't have other well-organized means to do a "structured query", I guess you'd say. It's a component of a pickUpNextTip command, or really pickUpNextTip concept, that will need to be in transfers.

Sorry, I'm not following what you mean by "structured query." I might be missing context. I've seen AUTH-1063 and #16602 but I don't see the part that a separate command helps with. Should I be looking somewhere else?

I guess I want to pass the question back at you as to whether error recovery or any other EXEC work will need a client to fetch the next tip from engine.

Possibly "yes," but if so, I am like 90% sure we will want to implement that as a safe, clean, read-only GET request, instead of running a live command with a POST request and having to contend with stuff like play/pause/door-open run states.

api/src/opentrons/protocol_engine/commands/get_next_tip.py Outdated Show resolved Hide resolved
api/src/opentrons/protocol_engine/commands/get_next_tip.py Outdated Show resolved Hide resolved
api/src/opentrons/protocol_engine/commands/get_next_tip.py Outdated Show resolved Hide resolved
Copy link
Member

@sanni-t sanni-t left a comment

Choose a reason for hiding this comment

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

Looks great!

Comment on lines 1125 to 1126
tipOriginWell: str = Field(
..., description="The (starting) well name of the next available tip(s)."
Copy link
Member

Choose a reason for hiding this comment

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

Continuing with Max's point, wouldn't 'origin' be yet another term that means the same thing as 'primary' and 'starting' tip? Is it used somewhere already or will it be a new term? Also the description says 'starting well name..'.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I came up with this with Casey but yeah, I see your point in muddying the waters with another term. I'll change it to tipStartingWell for consistency

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.

Agreed with Sanniti but otherwise LGTM, thanks!

public=GetNextTipResult(
nextTipInfo=NoTipAvailable(
noTipReason=NoTipReason.STARTING_TIP_WITH_PARTIAL,
message="Cannot automatically resolve next tip with starting tip and partial tip configuration.",
Copy link
Contributor

Choose a reason for hiding this comment

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

If this limitation is to match another limitation elsewhere in our codebase, it might be worth a comment here to describe where the underlying limitation comes from, to make things less confusing if the underlying limitation gets fixed.

@jbleon95 jbleon95 merged commit 1b95421 into edge Dec 9, 2024
48 checks passed

if (
starting_tip_name is not None
and nozzle_map.configuration != NozzleConfigurationType.FULL
Copy link
Contributor

Choose a reason for hiding this comment

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

What does NozzleConfigurationType.FULL mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The pipette is configured to pick up all tips on each channel

next_tip = NextTipInfo(labwareId=labware_id, tipStartingWell=well_name)
break
# After the first tip rack is exhausted, starting tip no longer applies
starting_tip_name = None
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, so this implementation does not try to remember which tiprack has available tips? It just loops through all the tipracks on every call?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It'll look at all tip racks given, in order, regardless of if it's completely empty. This is similar to how the API code figures out next tip (it never unassigns a tip rack once its been depleted)

decoy.when(
state_view.tips.get_next_tip(
labware_id="123",
num_tips=42,
Copy link
Contributor

Choose a reason for hiding this comment

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

So in this test, the caller is requesting the next tip for a hypothetical pipette that has 42 tips?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I use that to make it clear that this test is not testing veracity of testing the get_next_tip logic, but is passing the expected values to this function

starting_tip_name=None,
nozzle_map=mock_nozzle_map,
)
).then_return("foo")
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, it might be nice if this test could demonstrate the code working when there are multiple labwares and the first one doesn't have any tips left, because I'm kind of curious how your for-loop in the implementation would work.

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.

5 participants