-
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
fix(engine): pause hardware API when engine is paused #10882
Conversation
Codecov Report
@@ Coverage Diff @@
## edge #10882 +/- ##
=======================================
Coverage 73.73% 73.73%
=======================================
Files 2076 2076
Lines 57321 57321
Branches 5724 5724
=======================================
Hits 42266 42266
Misses 13820 13820
Partials 1235 1235
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.
I ended up not having a chance to test this on hardware today—sorry!
I've been staring at this for a little bit, but I'm having a pretty hard time following how our play/pause door-opened/door-closed actions flow through all the different layers—runner, engine, plugin, and hardware.
One thing on my mind is this behavior you described in the comment about a run that's played for the first time immediately becoming blocked by the door before any movement happens. It's not clear to me how we're implementing that.
Actually, it sort of looks to me like the legacy plugin is competing with ProtocolEngine.play()
?
ProtocolEngine.play()
will doself._action_dispatcher.dispatch()
, which will give the legacy plugin a chance to run first (I think?)- The plugin will see that the door is blocking, and pause the hardware API
ProtocolEngine.play()
will start the queue workerProtocolEngine.play()
will causeself._hardware_api.resume()
, undoing what the legacy plugin did?
I think @SyntaxColoring is right, this PR leaves the |
@@ -12,32 +10,18 @@ class PauseManager: | |||
timer runs out) and the pause resume (trigged by user via the app). | |||
""" | |||
|
|||
def __init__(self, door_state: DoorState) -> None: | |||
def __init__(self) -> 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.
I checked in with @sfoster1 on this one: the HW API would rather simply report door open and close events. This lines up nicely with a desire on our end to stop deeply nesting access to the config singleton.
Given that this PR gives the ProtocolEngine
enough information to reject an invalid resume while the door is open, I removed the unnecessary door logic from PauseManager
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.
My opinion isn't a super informed one, but I'm a big fan of this. It seems way easier to reason about the HW API if we're always controlling it and it's never controlling itself independently.
@@ -291,7 +291,7 @@ class ModuleView(HasState[ModuleState]): | |||
|
|||
_state: ModuleState | |||
|
|||
def __init__(self, state: ModuleState, virtualize_modules: bool) -> 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.
Argument was completely unused
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.
Code makes sense to me with the exception of a removal to queue_worker.start()
. I'll also test this on a robot and report results.
@@ -12,32 +10,18 @@ class PauseManager: | |||
timer runs out) and the pause resume (trigged by user via the app). | |||
""" | |||
|
|||
def __init__(self, door_state: DoorState) -> None: | |||
def __init__(self) -> 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.
My opinion isn't a super informed one, but I'm a big fan of this. It seems way easier to reason about the HW API if we're always controlling it and it's never controlling itself independently.
@@ -107,14 +109,19 @@ def play(self) -> None: | |||
PlayAction(requested_at=requested_at) | |||
) | |||
self._action_dispatcher.dispatch(action) | |||
self._queue_worker.start() |
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.
Is this an intentional removal of self._queue_worker.start()
?
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.
@SyntaxColoring @mcous I saw it the tests that instead we are now calling hardware_api.resume(HardwarePauseType.PAUSE)
hardware_api.pause(HardwarePauseType.PAUSE)?
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 intentional; queue_worker.start
is called in __init__
so all subsequent calls will no-op
@@ -29,7 +34,9 @@ async def create( | |||
return LiveProtocolSession( | |||
configuration=configuration, | |||
instance_meta=instance_meta, | |||
protocol_engine=await create_protocol_engine(configuration.hardware), | |||
protocol_engine=await create_protocol_engine( |
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.
@mcous you prob notices codecov
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.
Changes look good to me and I love the amount of code we got to remove! would love to see what race condition you guys were talking about before you made these changes
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.
Reviewed in a call w/ @mcous and the changes look great!
Tested on a robot with this protocol. metadata = {"apiLevel": "2.12"}
def run(protocol):
pipette = protocol.load_instrument("p300_multi_gen2", mount="left")
lw1 = protocol.load_labware("opentrons_96_tiprack_300ul", 1)
lw2 = protocol.load_labware("opentrons_96_tiprack_300ul", 3)
for i in range(10):
protocol.pause()
for i in range(2):
pipette.move_to(lw1["A1"].top())
pipette.move_to(lw2["A12"].top())
{
"id": "command.PAUSE-1",
"key": "command.PAUSE-1",
"commandType": "waitForResume",
"createdAt": "2022-06-29T16:18:20.701854+00:00",
"startedAt": "2022-06-29T16:18:20.701854+00:00",
"completedAt": "2022-06-29T16:18:20.703251+00:00",
"status": "succeeded",
"params": {}
},
{
"id": "command.MOVE_TO-4",
"key": "command.MOVE_TO-4",
"commandType": "custom",
"createdAt": "2022-06-29T16:18:20.711125+00:00",
"startedAt": "2022-06-29T16:18:20.711125+00:00",
"status": "running",
"params": {
"legacyCommandType": "command.MOVE_TO",
"legacyCommandText": "Moving to A1 of Opentrons 96 Tip Rack 300 µL on 1"
}
} |
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 great from an embedded point of view - glad to just be providing events and letting someone else worry about it.
Expected behavior with PAPIv2, given how pauses work. The This behavior is not the case with JSONv6 protocols, where |
Overview
This PR adds calls to
hardware_api.pause
andhardware_api.resume
whenengine.pause
andengine.resume
are called, respectively.Closes #7923
Changelog
Review requests
This PR needs to be tested on actual hardware rather than the simluator or emulator.
We need to check these acceptance criteria with both PAPIv2 protocols and JSONv6 protocols, because some functionality that was scoped specifically to PAPIv2 protocols has now moved into the
ProtocolEngine
proper.running
statepaused
statePause when door is opened
This ticket shouldn't affect "pause on door stop" behavior for PAPIv2 protocols. In advance of #9125, we should check that this PR does not negatively affect our expected PAPIv2 behavior. These tests are not expected to work on JSONv6 protocols until 9125 is completed.
intent: setup
) commandsplay
action) starts the run in a paused stateplay
action) will reject if the door is still openplay
action is issued, the run startsplay
action) will reject if the door is still openplay
action is issued, the run resumesRisk assessment
I started today pretty nervous about this, but once I got past the surface and looked at the ProtocolEngine state behaviors we've already implemented around door state, I'm feeling pretty confident about this!