Skip to content

Commit

Permalink
Merge branch 'abr-scale-googlesheet' of https://github.com/Opentrons/…
Browse files Browse the repository at this point in the history
…opentrons into abr-scale-googlesheet
  • Loading branch information
rclarke0 committed Mar 19, 2024
2 parents 2427470 + b9ef31b commit 4a49123
Show file tree
Hide file tree
Showing 70 changed files with 2,099 additions and 461 deletions.
233 changes: 109 additions & 124 deletions RELEASING.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions api-client/src/runs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,14 @@ export interface Runs {
export const RUN_ACTION_TYPE_PLAY: 'play' = 'play'
export const RUN_ACTION_TYPE_PAUSE: 'pause' = 'pause'
export const RUN_ACTION_TYPE_STOP: 'stop' = 'stop'
export const RUN_ACTION_TYPE_RESUME_FROM_RECOVERY: 'resume-from-recovery' =
'resume-from-recovery'

export type RunActionType =
| typeof RUN_ACTION_TYPE_PLAY
| typeof RUN_ACTION_TYPE_PAUSE
| typeof RUN_ACTION_TYPE_STOP
| typeof RUN_ACTION_TYPE_RESUME_FROM_RECOVERY

export interface RunAction {
id: string
Expand Down
3 changes: 3 additions & 0 deletions api/.flake8
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ extend-ignore =
ANN102
# do not require docstring for __init__, put them on the class
D107,
# Don't forbid the function signature from being mentioned in the first line of the
# docstring. It tends to raise false positives when referring to other functions.
D402,

# configure flake8-docstrings
# https://pypi.org/project/flake8-docstrings/
Expand Down
8 changes: 8 additions & 0 deletions api/src/opentrons/protocol_engine/actions/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ class StopAction:
from_estop: bool = False


@dataclass(frozen=True)
class ResumeFromRecoveryAction:
"""See `ProtocolEngine.resume_from_recovery()`."""

pass


@dataclass(frozen=True)
class FinishErrorDetails:
"""Error details for the payload of a FinishAction or HardwareStoppedAction."""
Expand Down Expand Up @@ -203,6 +210,7 @@ class SetPipetteMovementSpeedAction:
PlayAction,
PauseAction,
StopAction,
ResumeFromRecoveryAction,
FinishAction,
HardwareStoppedAction,
DoorChangeAction,
Expand Down
8 changes: 8 additions & 0 deletions api/src/opentrons/protocol_engine/protocol_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from contextlib import AsyncExitStack
from logging import getLogger
from typing import Dict, Optional, Union
from opentrons.protocol_engine.actions.actions import ResumeFromRecoveryAction

from opentrons.protocols.models import LabwareDefinition
from opentrons.hardware_control import HardwareControlAPI
Expand Down Expand Up @@ -159,6 +160,13 @@ def pause(self) -> None:
self._action_dispatcher.dispatch(action)
self._hardware_api.pause(HardwarePauseType.PAUSE)

def resume_from_recovery(self) -> None:
"""Resume normal protocol execution after the engine was `AWAITING_RECOVERY`."""
action = self._state_store.commands.validate_action_allowed(
ResumeFromRecoveryAction()
)
self._action_dispatcher.dispatch(action)

def add_command(self, request: commands.CommandCreate) -> commands.Command:
"""Add a command to the `ProtocolEngine`'s queue.
Expand Down
37 changes: 27 additions & 10 deletions api/src/opentrons/protocol_engine/state/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from opentrons.ordered_set import OrderedSet

from opentrons.hardware_control.types import DoorState
from opentrons.protocol_engine.actions.actions import ResumeFromRecoveryAction

from ..actions import (
Action,
Expand Down Expand Up @@ -665,10 +666,22 @@ def get_is_terminal(self) -> bool:
"""Get whether engine is in a terminal state."""
return self._state.run_result is not None

def validate_action_allowed(
def validate_action_allowed( # noqa: C901
self,
action: Union[PlayAction, PauseAction, StopAction, QueueCommandAction],
) -> Union[PlayAction, PauseAction, StopAction, QueueCommandAction]:
action: Union[
PlayAction,
PauseAction,
StopAction,
ResumeFromRecoveryAction,
QueueCommandAction,
],
) -> Union[
PlayAction,
PauseAction,
StopAction,
ResumeFromRecoveryAction,
QueueCommandAction,
]:
"""Validate whether a given control action is allowed.
Returns:
Expand All @@ -681,6 +694,17 @@ def validate_action_allowed(
SetupCommandNotAllowedError: The engine is running, so a setup command
may not be added.
"""
if self.get_status() == EngineStatus.AWAITING_RECOVERY:
# While we're developing error recovery, we'll conservatively disallow
# all actions, to avoid putting the engine in weird undefined states.
# We'll allow specific actions here as we flesh things out and add support
# for them.
raise NotImplementedError()

if isinstance(action, ResumeFromRecoveryAction):
# https://opentrons.atlassian.net/browse/EXEC-301
raise NotImplementedError()

if self._state.run_result is not None:
raise RunStoppedError("The run has already stopped.")

Expand All @@ -701,13 +725,6 @@ def validate_action_allowed(
"Setup commands are not allowed after run has started."
)

elif self.get_status() == EngineStatus.AWAITING_RECOVERY:
# While we're developing error recovery, we'll conservatively disallow
# all actions, to avoid putting the engine in weird undefined states.
# We'll allow specific actions here as we flesh things out and add support
# for them.
raise NotImplementedError()

return action

def get_status(self) -> EngineStatus:
Expand Down
4 changes: 4 additions & 0 deletions api/src/opentrons/protocol_runner/protocol_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ async def stop(self) -> None:
post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE,
)

def resume_from_recovery(self) -> None:
"""See `ProtocolEngine.resume_from_recovery()`."""
self._protocol_engine.resume_from_recovery()

@abstractmethod
async def run(
self,
Expand Down
20 changes: 17 additions & 3 deletions api/tests/opentrons/protocol_engine/state/test_command_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
StopAction,
QueueCommandAction,
)
from opentrons.protocol_engine.actions.actions import ResumeFromRecoveryAction

from opentrons.protocol_engine.state.commands import (
CommandState,
Expand Down Expand Up @@ -322,8 +323,14 @@ class ActionAllowedSpec(NamedTuple):
"""Spec data to test CommandView.validate_action_allowed."""

subject: CommandView
action: Union[PlayAction, PauseAction, StopAction, QueueCommandAction]
expected_error: Optional[Type[errors.ProtocolEngineError]]
action: Union[
PlayAction,
PauseAction,
StopAction,
QueueCommandAction,
ResumeFromRecoveryAction,
]
expected_error: Optional[Type[Exception]]


action_allowed_specs: List[ActionAllowedSpec] = [
Expand Down Expand Up @@ -455,14 +462,21 @@ class ActionAllowedSpec(NamedTuple):
),
expected_error=errors.SetupCommandNotAllowedError,
),
# Resuming from error recovery is not implemented yet.
# https://opentrons.atlassian.net/browse/EXEC-301
ActionAllowedSpec(
subject=get_command_view(),
action=ResumeFromRecoveryAction(),
expected_error=NotImplementedError,
),
]


@pytest.mark.parametrize(ActionAllowedSpec._fields, action_allowed_specs)
def test_validate_action_allowed(
subject: CommandView,
action: Union[PlayAction, PauseAction, StopAction],
expected_error: Optional[Type[errors.ProtocolEngineError]],
expected_error: Optional[Type[Exception]],
) -> None:
"""It should validate allowed play/pause/stop actions."""
expectation = pytest.raises(expected_error) if expected_error else does_not_raise()
Expand Down
19 changes: 19 additions & 0 deletions api/tests/opentrons/protocol_engine/test_protocol_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from opentrons_shared_data.robot.dev_types import RobotType
from opentrons.ordered_set import OrderedSet
from opentrons.protocol_engine.actions.actions import ResumeFromRecoveryAction

from opentrons.types import DeckSlotName
from opentrons.hardware_control import HardwareControlAPI, OT2HardwareControlAPI
Expand Down Expand Up @@ -427,6 +428,24 @@ def test_pause(
)


def test_resume_from_recovery(
decoy: Decoy,
state_store: StateStore,
action_dispatcher: ActionDispatcher,
subject: ProtocolEngine,
) -> None:
"""It should dispatch a ResumeFromRecoveryAction."""
expected_action = ResumeFromRecoveryAction()

decoy.when(
state_store.commands.validate_action_allowed(expected_action)
).then_return(expected_action)

subject.resume_from_recovery()

decoy.verify(action_dispatcher.dispatch(expected_action))


@pytest.mark.parametrize("drop_tips_after_run", [True, False])
@pytest.mark.parametrize("set_run_status", [True, False])
@pytest.mark.parametrize(
Expand Down
25 changes: 22 additions & 3 deletions api/tests/opentrons/protocol_runner/test_protocol_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def live_runner_subject(
(None, LiveRunner),
],
)
async def test_create_protocol_runner(
def test_create_protocol_runner(
protocol_engine: ProtocolEngine,
hardware_api: HardwareAPI,
task_queue: TaskQueue,
Expand Down Expand Up @@ -203,7 +203,7 @@ async def test_create_protocol_runner(
(lazy_fixture("live_runner_subject")),
],
)
async def test_play_starts_run(
def test_play_starts_run(
decoy: Decoy,
protocol_engine: ProtocolEngine,
task_queue: TaskQueue,
Expand All @@ -223,7 +223,7 @@ async def test_play_starts_run(
(lazy_fixture("live_runner_subject")),
],
)
async def test_pause(
def test_pause(
decoy: Decoy,
protocol_engine: ProtocolEngine,
subject: AnyRunner,
Expand Down Expand Up @@ -286,6 +286,25 @@ async def test_stop_when_run_never_started(
)


@pytest.mark.parametrize(
"subject",
[
(lazy_fixture("json_runner_subject")),
(lazy_fixture("legacy_python_runner_subject")),
(lazy_fixture("live_runner_subject")),
],
)
def test_resume_from_recovery(
decoy: Decoy,
protocol_engine: ProtocolEngine,
subject: AnyRunner,
) -> None:
"""It should call `resume_from_recovery()` on the underlying engine."""
subject.resume_from_recovery()

decoy.verify(protocol_engine.resume_from_recovery(), times=1)


async def test_run_json_runner(
decoy: Decoy,
hardware_api: HardwareAPI,
Expand Down
1 change: 1 addition & 0 deletions app/src/assets/localization/en/protocol_details.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"location": "location",
"modules": "modules",
"name": "Name",
"num_choices": "{{num}} choices",
"no_available_robots_found": "No available robots found",
"no_parameters": "No parameters specified in this protocol",
"no_summary": "no summary specified for this protocol.",
Expand Down
8 changes: 8 additions & 0 deletions app/src/assets/localization/en/protocol_setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"configured": "configured",
"confirm_heater_shaker_module_modal_description": "Before the run begins, module should have both anchors fully extended for a firm attachment. The thermal adapter should be attached to the module. ",
"confirm_heater_shaker_module_modal_title": "Confirm Heater-Shaker Module is attached",
"confirm_values": "Confirm values",
"connect_all_hardware": "Connect and calibrate all hardware first",
"connect_all_mod": "Connect all modules first",
"connection_info_not_available": "Connection info not available once run has started",
Expand Down Expand Up @@ -168,15 +169,18 @@
"no_usb_required": "No USB required",
"not_calibrated": "Not calibrated yet",
"not_configured": "not configured",
"off": "off",
"off_deck": "Off deck",
"offset_data": "Offset Data",
"offsets_applied_plural": "{{count}} offsets applied",
"offsets_applied": "{{count}} offset applied",
"on": "on",
"on_adapter_in_mod": "on {{adapterName}} in {{moduleName}}",
"on_adapter": "on {{adapterName}}",
"on_deck": "On deck",
"on-deck_labware": "{{count}} on-deck labware",
"opening": "Opening...",
"parameters": "Parameters",
"pipette_mismatch": "Pipette generation mismatch.",
"pipette_missing": "Pipette missing",
"pipette_offset_cal_description_bullet_1": "Perform Pipette Offset calibration the first time you attach a pipette to a new mount.",
Expand Down Expand Up @@ -208,6 +212,10 @@
"recommended": "Recommended",
"required_instrument_calibrations": "required instrument calibrations",
"required_tip_racks_title": "Required Tip Length Calibrations",
"reset_parameter_values_body": "This will discard any changes you have made. All parameters will have their default values.",
"reset_parameter_values": "Reset parameter values?",
"reset_values": "Reset values",
"restore_default": "Restore default values",
"resolve": "Resolve",
"robot_cal_description": "Robot calibration establishes how the robot knows where it is in relation to the deck. Accurate Robot calibration is essential to run protocols successfully. Robot calibration has 3 parts: Deck calibration, Tip Length calibration and Pipette Offset calibration.",
"robot_cal_help_title": "How Robot Calibration Works",
Expand Down
39 changes: 12 additions & 27 deletions app/src/atoms/Chip/Chip.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import type { Story, Meta } from '@storybook/react'

export default {
title: 'ODD/Atoms/Chip',
argTypes: {
type: {
options: ['basic', 'error', 'info', 'neutral', 'success', 'warning'],
control: {
type: 'select',
},
defaultValue: 'basic',
},
},
component: Chip,
parameters: touchScreenViewport,
} as Meta
Expand All @@ -25,32 +34,8 @@ const Template: Story<ChipStorybookProps> = ({ ...args }) => (
</Flex>
)

export const Basic = Template.bind({})
Basic.args = {
export const ChipComponent = Template.bind({})
ChipComponent.args = {
type: 'basic',
text: 'Basic chip text',
}

export const Error = Template.bind({})
Error.args = {
type: 'error',
text: 'Not connected',
}

export const Success = Template.bind({})
Success.args = {
type: 'success',
text: 'Connected',
}

export const Warning = Template.bind({})
Warning.args = {
type: 'warning',
text: 'Missing 1 module',
}

export const Neutral = Template.bind({})
Neutral.args = {
type: 'neutral',
text: 'Not connected',
text: 'Chip component',
}
Loading

0 comments on commit 4a49123

Please sign in to comment.