-
Notifications
You must be signed in to change notification settings - Fork 180
feat(api): store labware objects in the stacker #18010
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
base: edge
Are you sure you want to change the base?
Conversation
3bac787
to
d116172
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## edge #18010 +/- ##
==========================================
- Coverage 61.87% 61.85% -0.02%
==========================================
Files 2989 2996 +7
Lines 232727 234328 +1601
Branches 20112 20421 +309
==========================================
+ Hits 143993 144949 +956
- Misses 88557 89202 +645
Partials 177 177
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
labware: LoadedLabware, | ||
) -> None: | ||
definitions_by_id[labware.id] = definition | ||
display_names_by_id[labware.id] = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be None? we can get int from the definition?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This field is just for the user-specified display name, if any - the label
argument to ProtocolContext.load_labware
. At least that's how it is in the load labware engine command
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactor and new features look really great overall, thanks Seth! Stacker common is a bit massive now, but I think it makes sense for the logic to live there as opposed to split between retrieve/unsafe retrieve and store as it was. Logistical question, do we want to wait on this until the PAPI PR is up and merge them into eachother first before merging this to edge?
The structure for Labware group and location sequencing logic, as well as the changes to Store and Retrieve to track the contained labware groups make sense. Some notes below on some things I wanted to verify and also where we might need to change the app end logic since retrieve isn't the "loader" for labware anymore.
return group.lid.locationSequence | ||
|
||
|
||
def _check_one_preloaded_labware_known( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This all seems like solid validation, should be zero cases of mixed state labware groups anyways so nice!
return False | ||
|
||
|
||
def _check_one_preloaded_labware( # noqa: C901 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, but just wanted to verify one thing regarding lids. We are validating that lids must be on a labware location, but not that solo lids must be off deck. Are we fully planning to not distribute lids/lid stacks out of the stacker? We can always make that change later, not blocking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh that's sort of a nightmare. I think we should leave that to a followup. I guess we could implement it in one of the following ways:
- in a stacker, the primary labware is a lid stack system object (0 height), each of which contains one lid. downside: can only retrieve one lid at a time. upside: follows our current system all the way through
- in a stacker, lids can be primary labware, so the stacker contains many
StackerStoredLabwareGroup
s with one lid as the primary. downside: can only retrieve one lid at a time, doesn't follow our current pattern. upside: no synthetic labware - we make a new kind of
StackedStoredLabwareGroup
or enhance the current one to allow for multiple lids in some way. downside: big change, upside: follows our current pattern and can retrieve more than one lid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(2) sounds the easiest to implement but with restrictions
(3) is extendable and will follow our retrieve guidelines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely leaning approach 3, given users will want to move lids in "stacks". From a functional standpoint though, they'd have to move each lid off their stack to its destination one at a time, or rebuild the stack piecemeal elsewhere on the deck lest they leave their stacker unusable until all labware is consumed from the stack. The other option would be giving them some way of moving the whole stack at once (not a fan).
.set_batch_loaded_labware( | ||
definitions_by_id=definitions_by_id, | ||
offset_ids_by_id=offset_ids_by_id, | ||
display_names_by_id=display_names_by_id, | ||
new_locations_by_id=new_locations_by_id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine as is, but last time I was modifying how we split out some of the batch loaded labware behavior I was wondering if it would be better to just send a set of LoadedLabware objects right down the line through state update all the way to the labware store. No need to gut it now, but these various dicts just do the "breaking up" work here instead of down at the handler in the end, so we could in theory save all this for that end step. I think the only reason I didn't at the time is that the new_location_by_id dict had to exist for location sequences so I left it all be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah. we have a soft preference to have the data in state updates and results be native data types, but I think that's just people pattern matching - don't think there's a big reason.
self._state_view, | ||
[ | ||
NotOnDeckLocationSequenceComponent( | ||
logicalLocationName=SYSTEM_LOCATION |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent, this aligns with the way in which we spawn new lid stack objects onto the deck during a move lid to empty deck slot command.
lid_def, | ||
) | ||
.update_flex_stacker_labware_pool_count(params.moduleId, count) | ||
state_update, new_contained_labware = await build_or_assign_labware_to_hopper( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will need to update the client and our starting deck state logic to be aware that this is our new "source" of a load labware type of action instead of retrieve.
] | ||
|
||
|
||
def build_retrieve_labware_move_updates( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you explain the method name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
build (retrieve labware) (move updates)
..., description="The number of labware loaded into the stacker labware pool." | ||
..., description="The number of labware now stored in the hopper." | ||
) | ||
originalPrimaryLabwareLocationSequences: ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might be missing something but why do we need this prop in setStoredLabware command?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It lets clients tell the engine to build its own labware without specifying all the ids, which the PAPI will want to do.
I think we want to wait on this until the PAPI PR is up so that we can actually test it, but I'd prefer to merge them independently so there isn't just a(n even more) giant lump of a commit in history for ever |
2801588
to
ea1ac45
Compare
6b24dbc
to
5fc54ed
Compare
5fc54ed
to
1ea73fb
Compare
group, | ||
self._state_view, | ||
[ | ||
NotOnDeckLocationSequenceComponent( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!
module_id=params.moduleId, count=stacker_state.pool_count + 1 | ||
state_update.update_flex_stacker_contained_labware( | ||
module_id=params.moduleId, | ||
contained_labware_bottom_first=( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
beautiful ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a lot to read but its perfect. nice job! once ci passes of course :-)
return False | ||
|
||
|
||
def _check_one_preloaded_labware( # noqa: C901 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(2) sounds the easiest to implement but with restrictions
(3) is extendable and will follow our retrieve guidelines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for tackling this – it is a lot of work!!
All of the labware-handling logic makes sense to me. I can help test on hardware tomorrow if no one has gotten to it yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test cases look good, the stacker common tests are very extensive and should give good resilience. Thank you for addressing my former comments as well, I especially think it makes sense to leave "independent lids" alone for now however I favor approach 3 from your suggestions.
A dream can still be beautiful, even if it is different!
return False | ||
|
||
|
||
def _check_one_preloaded_labware( # noqa: C901 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely leaning approach 3, given users will want to move lids in "stacks". From a functional standpoint though, they'd have to move each lid off their stack to its destination one at a time, or rebuild the stack piecemeal elsewhere on the deck lest they leave their stacker unusable until all labware is consumed from the stack. The other option would be giving them some way of moving the whole stack at once (not a fan).
We had this beautiful dream: the stacker wouldn't store labware. Instead, the stacker would know what kind of labware it should store, and how many of those labware it is storing, and objects would be created when you retrieved stuff. The problem with that is in the physical world there are labware objects in the stacker, and if we're not modeling them you can't know the state of those physical objects. So we have to reverse this decision and store the identity of the labware in the stacker.
This PR is the set of changes to accomplish that in the engine and account for it in the python API; the ts bindings; and at least somewhat in the app interface that already exists. It won't add any new API or app stuff.
The way that this works is:
InStackerHopper(moduleId)
setStoredLabware
andfill
now handle making sure all the labware exists. They take lists of ids for PD compliance and also so that you can predefine labware if you want. They also still have a count argument. You cancount
, and the command will create that many labware with arbitrary IDsOFF_DECK
or there's an errorretrieve
doesn't create labware anymoreempty
, it's moved toOFF_DECK
fill
,setStoredLabware
, andempty
all now specify lots of labware location sequences and ids in their results because they create or move labware where they previously do not. Whenfill
andsetStoredLabware
create labware, they gloss those labware as having an original position descending toSYSTEM
. Otherwise, they're the real original locations.to come out of draft
Make commit names that aren't so sillythat's what the squash is forto merge
review requests
in addition to the standard stuff,