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 stacker module context and protocol command skeletons #17206

Open
wants to merge 22 commits into
base: edge
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b9f150c
create module live data typed dict i guess
ahiuchingau Jan 9, 2025
5d94f16
create live data validator to narrow down type for each module
ahiuchingau Jan 9, 2025
281fb67
fix types in robot-server and tests
ahiuchingau Jan 9, 2025
79fa673
Merge branch 'edge' into module_live-data
ahiuchingau Jan 9, 2025
70a3461
fix some linter errors
ahiuchingau Jan 9, 2025
5d65076
add flex stacker data as rebase
ahiuchingau Jan 9, 2025
b0350c1
update command snapshots
ahiuchingau Jan 9, 2025
c839e76
add flex stacker module context and core
ahiuchingau Jan 6, 2025
d151673
add module core functions
ahiuchingau Jan 6, 2025
1d64f1e
add flex stacker protocol engine command skeletons
ahiuchingau Jan 6, 2025
3536b06
add module substate
ahiuchingau Jan 6, 2025
6fbe621
add stacker module type to protocol engine
ahiuchingau Jan 6, 2025
a27ace9
fix failed tests in protocol engine
ahiuchingau Jan 7, 2025
fda927e
consolidate test_load_module cases and test all ot3 modules
ahiuchingau Jan 7, 2025
982ed1b
fix linter error
ahiuchingau Jan 8, 2025
aef78d7
too complex
ahiuchingau Jan 8, 2025
5d9c591
fix rebase errors
ahiuchingau Jan 9, 2025
83cb45f
update flexstacker model name to match edge
ahiuchingau Jan 9, 2025
1fe24dd
add substate update for flex stacker
ahiuchingau Jan 9, 2025
6ae32b1
update addressable area name
ahiuchingau Jan 9, 2025
de23921
Merge branch 'edge' into EXEC-1079-module-context
ahiuchingau Jan 9, 2025
d35f9eb
update modele core to execute protocol engine command
ahiuchingau Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion api/src/opentrons/hardware_control/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
"AbsorbanceReader",
"AbsorbanceReaderStatus",
"AbsorbanceReaderDisconnectedError",
"ModuleDisconnectedCallback",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This modules.ModuleDisconnectedCallback type got removed as the type has not been mported to this file, and hence, will not affect the rest of the code

"FlexStacker",
"FlexStackerStatus",
"PlatformState",
Expand Down
2 changes: 2 additions & 0 deletions api/src/opentrons/protocol_api/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
AbstractHeaterShakerCore,
AbstractMagneticBlockCore,
AbstractAbsorbanceReaderCore,
AbstractFlexStackerCore,
)
from .protocol import AbstractProtocol
from .well import AbstractWellCore
Expand All @@ -27,5 +28,6 @@
HeaterShakerCore = AbstractHeaterShakerCore
MagneticBlockCore = AbstractMagneticBlockCore
AbsorbanceReaderCore = AbstractAbsorbanceReaderCore
FlexStackerCore = AbstractFlexStackerCore
RobotCore = AbstractRobot
ProtocolCore = AbstractProtocol[InstrumentCore, LabwareCore, ModuleCore]
23 changes: 23 additions & 0 deletions api/src/opentrons/protocol_api/core/engine/module_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
AbstractHeaterShakerCore,
AbstractMagneticBlockCore,
AbstractAbsorbanceReaderCore,
AbstractFlexStackerCore,
)
from .exceptions import InvalidMagnetEngageHeightError

Expand Down Expand Up @@ -692,3 +693,25 @@ def is_lid_on(self) -> bool:
self.module_id
)
return abs_state.is_lid_on


class FlexStackerCore(ModuleCore, AbstractFlexStackerCore):
"""Flex Stacker core logic implementation for Python protocols."""

_sync_module_hardware: SynchronousAdapter[hw_modules.FlexStacker]

def retrieve(self) -> None:
"""Retrieve a labware from the bottom of the Flex Stacker's stack."""
self._engine_client.execute_command(
cmd.flex_stacker.RetrieveParams(
moduleId=self.module_id,
)
)

def store(self) -> None:
"""Store a labware at the bottom of the Flex Stacker's stack."""
self._engine_client.execute_command(
cmd.flex_stacker.StoreParams(
moduleId=self.module_id,
)
)
19 changes: 19 additions & 0 deletions api/src/opentrons/protocol_api/core/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,22 @@ def open_lid(self) -> None:
@abstractmethod
def is_lid_on(self) -> bool:
"""Return True if the Absorbance Reader's lid is currently closed."""


class AbstractFlexStackerCore(AbstractModuleCore):
"""Core control interface for an attached Flex Stacker."""

MODULE_TYPE: ClassVar = ModuleType.FLEX_STACKER

@abstractmethod
def get_serial_number(self) -> str:
"""Get the module's unique hardware serial number."""

@abstractmethod
def retrieve(self) -> None:
"""Release and return a labware at the bottom of the labware stack."""

@abstractmethod
def store(self) -> None:
"""Store a labware at the bottom of the labware stack."""
pass
32 changes: 32 additions & 0 deletions api/src/opentrons/protocol_api/module_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
HeaterShakerCore,
MagneticBlockCore,
AbsorbanceReaderCore,
FlexStackerCore,
)
from .core.core_map import LoadedCoreMap
from .core.engine import ENGINE_CORE_API_VERSION
Expand Down Expand Up @@ -1098,3 +1099,34 @@ def read(
:returns: A dictionary of wavelengths to dictionary of values ordered by well name.
"""
return self._core.read(filename=export_filename)


class FlexStackerContext(ModuleContext):
"""An object representing a connected Flex Stacker module.

It should not be instantiated directly; instead, it should be
created through :py:meth:`.ProtocolContext.load_module`.

.. versionadded:: 2.23
"""

_core: FlexStackerCore

@property
@requires_version(2, 23)
def serial_number(self) -> str:
"""Get the module's unique hardware serial number."""
return self._core.get_serial_number()

@requires_version(2, 23)
def retrieve(self) -> None:
"""Release and return a labware at the bottom of the labware stack."""
self._core.retrieve()

@requires_version(2, 23)
def store(self, labware: Labware) -> None:
"""Store a labware at the bottom of the labware stack.

:param labware: The labware object to store.
"""
self._core.store()
2 changes: 2 additions & 0 deletions api/src/opentrons/protocol_engine/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""

from . import absorbance_reader
from . import flex_stacker
from . import heater_shaker
from . import magnetic_module
from . import temperature_module
Expand Down Expand Up @@ -614,6 +615,7 @@
# hardware control command models
# hardware module command bundles
"absorbance_reader",
"flex_stacker",
"heater_shaker",
"magnetic_module",
"temperature_module",
Expand Down
11 changes: 11 additions & 0 deletions api/src/opentrons/protocol_engine/commands/command_unions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .movement_common import StallOrCollisionError

from . import absorbance_reader
from . import flex_stacker
from . import heater_shaker
from . import magnetic_module
from . import temperature_module
Expand Down Expand Up @@ -430,6 +431,8 @@
absorbance_reader.OpenLid,
absorbance_reader.Initialize,
absorbance_reader.ReadAbsorbance,
flex_stacker.Retrieve,
flex_stacker.Store,
calibration.CalibrateGripper,
calibration.CalibratePipette,
calibration.CalibrateModule,
Expand Down Expand Up @@ -518,6 +521,8 @@
absorbance_reader.OpenLidParams,
absorbance_reader.InitializeParams,
absorbance_reader.ReadAbsorbanceParams,
flex_stacker.RetrieveParams,
flex_stacker.StoreParams,
calibration.CalibrateGripperParams,
calibration.CalibratePipetteParams,
calibration.CalibrateModuleParams,
Expand Down Expand Up @@ -604,6 +609,8 @@
absorbance_reader.OpenLidCommandType,
absorbance_reader.InitializeCommandType,
absorbance_reader.ReadAbsorbanceCommandType,
flex_stacker.RetrieveCommandType,
flex_stacker.StoreCommandType,
calibration.CalibrateGripperCommandType,
calibration.CalibratePipetteCommandType,
calibration.CalibrateModuleCommandType,
Expand Down Expand Up @@ -691,6 +698,8 @@
absorbance_reader.OpenLidCreate,
absorbance_reader.InitializeCreate,
absorbance_reader.ReadAbsorbanceCreate,
flex_stacker.RetrieveCreate,
flex_stacker.StoreCreate,
calibration.CalibrateGripperCreate,
calibration.CalibratePipetteCreate,
calibration.CalibrateModuleCreate,
Expand Down Expand Up @@ -786,6 +795,8 @@
absorbance_reader.OpenLidResult,
absorbance_reader.InitializeResult,
absorbance_reader.ReadAbsorbanceResult,
flex_stacker.RetrieveResult,
flex_stacker.StoreResult,
calibration.CalibrateGripperResult,
calibration.CalibratePipetteResult,
calibration.CalibrateModuleResult,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Command models for Flex Stacker commands."""

from .store import (
StoreCommandType,
StoreParams,
StoreResult,
Store,
StoreCreate,
)

from .retrieve import (
RetrieveCommandType,
RetrieveParams,
RetrieveResult,
Retrieve,
RetrieveCreate,
)


__all__ = [
# flexStacker/store
"StoreCommandType",
"StoreParams",
"StoreResult",
"Store",
"StoreCreate",
# flexStacker/retrieve
"RetrieveCommandType",
"RetrieveParams",
"RetrieveResult",
"Retrieve",
"RetrieveCreate",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Command models to retrieve a labware from a Flex Stacker."""
from __future__ import annotations
from typing import Optional, Literal, TYPE_CHECKING
from typing_extensions import Type

from pydantic import BaseModel, Field

from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
from ...errors.error_occurrence import ErrorOccurrence
from ...state import update_types

if TYPE_CHECKING:
from opentrons.protocol_engine.state.state import StateView
from opentrons.protocol_engine.execution import EquipmentHandler

RetrieveCommandType = Literal["flexStacker/retrieve"]


class RetrieveParams(BaseModel):
"""Input parameters for a labware retrieval command."""

moduleId: str = Field(
...,
description="Unique ID of the Flex Stacker.",
)


class RetrieveResult(BaseModel):
"""Result data from a labware retrieval command."""


class RetrieveImpl(AbstractCommandImpl[RetrieveParams, SuccessData[RetrieveResult]]):
"""Implementation of a labware retrieval command."""

def __init__(
self,
state_view: StateView,
equipment: EquipmentHandler,
**kwargs: object,
) -> None:
self._state_view = state_view
self._equipment = equipment

async def execute(self, params: RetrieveParams) -> SuccessData[RetrieveResult]:
"""Execute the labware retrieval command."""
state_update = update_types.StateUpdate()
stacker_substate = self._state_view.modules.get_flex_stacker_substate(
module_id=params.moduleId
)

# Allow propagation of ModuleNotAttachedError.
stacker = self._equipment.get_module_hardware_api(stacker_substate.module_id)

if stacker is not None:
await stacker.dispense()

return SuccessData(public=RetrieveResult(), state_update=state_update)


class Retrieve(BaseCommand[RetrieveParams, RetrieveResult, ErrorOccurrence]):
"""A command to retrieve a labware from a Flex Stacker."""

commandType: RetrieveCommandType = "flexStacker/retrieve"
params: RetrieveParams
result: Optional[RetrieveResult]

_ImplementationCls: Type[RetrieveImpl] = RetrieveImpl


class RetrieveCreate(BaseCommandCreate[RetrieveParams]):
"""A request to execute a Flex Stacker retrieve command."""

commandType: RetrieveCommandType = "flexStacker/retrieve"
params: RetrieveParams

_CommandCls: Type[Retrieve] = Retrieve
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Command models to retrieve a labware from a Flex Stacker."""
from __future__ import annotations
from typing import Optional, Literal, TYPE_CHECKING
from typing_extensions import Type

from pydantic import BaseModel, Field

from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
from ...errors.error_occurrence import ErrorOccurrence
from ...state import update_types

if TYPE_CHECKING:
from opentrons.protocol_engine.state.state import StateView
from opentrons.protocol_engine.execution import EquipmentHandler


StoreCommandType = Literal["flexStacker/store"]


class StoreParams(BaseModel):
"""Input parameters for a labware storage command."""

moduleId: str = Field(
...,
description="Unique ID of the flex stacker.",
)


class StoreResult(BaseModel):
"""Result data from a labware storage command."""


class StoreImpl(AbstractCommandImpl[StoreParams, SuccessData[StoreResult]]):
"""Implementation of a labware storage command."""

def __init__(
self,
state_view: StateView,
equipment: EquipmentHandler,
**kwargs: object,
) -> None:
self._state_view = state_view
self._equipment = equipment

async def execute(self, params: StoreParams) -> SuccessData[StoreResult]:
"""Execute the labware storage command."""
state_update = update_types.StateUpdate()
stacker_substate = self._state_view.modules.get_flex_stacker_substate(
module_id=params.moduleId
)

# Allow propagation of ModuleNotAttachedError.
stacker = self._equipment.get_module_hardware_api(stacker_substate.module_id)

if stacker is not None:
await stacker.store()

return SuccessData(public=StoreResult(), state_update=state_update)


class Store(BaseCommand[StoreParams, StoreResult, ErrorOccurrence]):
"""A command to store a labware in a Flex Stacker."""

commandType: StoreCommandType = "flexStacker/store"
params: StoreParams
result: Optional[StoreResult]

_ImplementationCls: Type[StoreImpl] = StoreImpl


class StoreCreate(BaseCommandCreate[StoreParams]):
"""A request to execute a Flex Stacker store command."""

commandType: StoreCommandType = "flexStacker/store"
params: StoreParams

_CommandCls: Type[Store] = Store
Loading
Loading