-
Notifications
You must be signed in to change notification settings - Fork 179
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, robot-server, app): Handle the Plate Reader lid while held by the gripper when the Estop is pressed. #16574
Conversation
…er is holding somewhere on the deck.
…rrentState endpoint.
…al to handle plate reader 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.
Nice first go at one of the more complicated parts of React/JS!
See comments. I think the serious things are:
- We're always creating a maintenance run when the estop modal is popped.
- We can use existing utils to do most of the heavy lifting in
usePlacePlateReaderLid
. - We're always closing the estop modal while plate reader commands may be running.
cafa0c5
to
2cb3765
Compare
…show the remote-controlled takeover modal when we are automatically moving the plate reader 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.
Nice job! I do like the e-stop control flow much more now.
(I only reviewed the app side of things).
closeModal={closeModal} | ||
isWaitingForResumeOperation={isWaitingForResumeOperation} | ||
setIsWaitingForResumeOperation={setIsWaitingForResumeOperation} |
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.
Two separate setters responsible for the same state seems strange to me, but I get that this code was here already, and I don't think it's worth addressing now.
7af2905
to
ddc24cb
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## edge #16574 +/- ##
==========================================
- Coverage 79.88% 74.02% -5.87%
==========================================
Files 120 43 -77
Lines 4410 3176 -1234
==========================================
- Hits 3523 2351 -1172
+ Misses 887 825 -62
Flags with carried forward coverage won't be shown. Click here to find out more. |
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.
Engine side logic looks good, nice work on this. A non-blocking question down below regarding the fallback case for how some of our geometry is calculated/resolved in this implementation that may effect future labware recovery behavior, but otherwise this looks great!
) | ||
if substate.lid_id is 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.
This looks good, should provide coverage for the current run setup bug.
final_offsets = self._state_view.labware.get_labware_gripper_offsets( | ||
labware_id, None | ||
) | ||
drop_offset = cast(Point, final_offsets.dropOffset) if final_offsets else 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.
Everything in the UnsafePlaceLabware command logic looks good, but just want to make sure I understand. When we're doing this None case here we are basically just lowering and releasing the labware in place + its movement center? So if we were between two slots, would we end up releasing the labware in a awkward deck space? Not really too much of an issue for this PR since the primary use case for now is plate reader lids which will always have resolved locations with final offsets.
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.
No, since these are just the dropoff offsets, in that case, we would navigate to the slot and drop off the labware +- some hardware stack-up margin. We should never be dropping labware in between 2 slots for example, unless the definition for that is way off.
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!! Just got a small question
await self._poller.stop() | ||
await self._driver.disconnect() | ||
"""Deactivate the module.""" | ||
pass |
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.
What does it mean when we deactivate
the module now?
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.
In the context of modules, the deactivate
function means we stop whatever target action we are doing, such as heating, shaking, etc. We don't have any actions other than reading for the plate reader, but these happen relatively quickly, so we don't have to worry about them. The problem here was that I thought deactivate meant stop and disconnect the module, which is not correct as deactivate
gets called when the estop is pressed. In the case of the plate reader it would disconnect and reconnect when the estop was pressed. This change fixes that behavior by removing the stop and disconnect to the cleanup
method.
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.
Some drive-by comments. No objections to any of this merging as-is if you need to get it in for the v8.2.0 feature freeze.
api/src/opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py
Outdated
Show resolved
Hide resolved
api/src/opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py
Outdated
Show resolved
Hide resolved
async def execute( | ||
self, params: UnsafePlaceLabwareParams | ||
) -> SuccessData[UnsafePlaceLabwareResult, None]: | ||
"""Place Labware.""" |
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.
Having not really been involved in this, I would find it helpful if one of these docstrings described when it's appropriate and legal for a client to use this unsafe
command, and when it's not.
i.e., why is this called unsafe/placeLabware
, and not just placeLabware
.
I would also consider that the unfamiliar audience is probably comparing this with moveLabware
and loadLabware
, so we should describe how it's distinct from those.
class RunCurrentState(BaseModel): | ||
"""Current details about a run.""" | ||
|
||
estopEngaged: bool = Field(..., description="") |
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.
Nit: If we don't have a description to offer, let's just leave description
unset instead of doing description=""
.
i.e. estopEngaged: bool
instead of estopEngaged: bool = Field(..., description="")
.
|
||
labwareId: str = Field(..., description="The ID of the labware to place.") | ||
location: OnDeckLabwareLocation = Field( | ||
..., description="The location the Plate Reade lid should be in." |
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.
..., description="The location the Plate Reade lid should be in." | |
..., description="The location the Plate Reader lid should be in." |
class PlaceLabwareState(BaseModel): | ||
"""Details the labware being placed by the gripper.""" | ||
|
||
labwareId: str = Field(..., description="The ID of the labware to place.") | ||
location: OnDeckLabwareLocation = Field( | ||
..., description="The location the Plate Reade lid should be in." | ||
) | ||
shouldPlaceDown: bool = Field( | ||
..., description="Whether the gripper should place down the labware." | ||
) | ||
|
||
|
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.
placeLabwareState.location
is defined here in terms of the plate reader lid, specifically, as opposed to labware locations in general. So, should we either:
- Rename
placeLabwareState
to something that reflects that it's Plate-Reader–specific, and is not meant to be used for labware in general - Or, redefine
placeLabwareState.location
to not be specifically about the plate reader lid
Overview
We don't want customers manually handling the Plate Reader lid as this can cause damage to the device when you press the estop while the gripper is holding the plate reader lid it will eventually home the gantry and hold onto the lid. When a new protocol is started the gripper jaws will open, causing the plate reader lid to fall and potentially get damaged. This pull request makes it so we automatically place the plate reader lid in its dock (staging area) when the robot resumes operating after physically and logically disengaging the estop.
Closes: PLAT-451
Test Plan and Hands on Testing
open/close_lid
commands still workplaceLabware
when the estop is pressedChangelog
unsafe/placeLabware
command to PE to place labware the gripper is already holding on to.placeLabwareState
to theruns/{run_id}/currentState
endpoint to inform the client of the plate reader lid.usePlacePlateReaderLid
hook on the client to conditionally call theunsafe/placeLabware
command when theplaceLabwareState
returns the appropriate values.Review requests
Risk assessment
med, this changes post-estop behavior.