Skip to content

Commit

Permalink
Lid position automatic validation and engine adjustment and labware m…
Browse files Browse the repository at this point in the history
…ovement fix
  • Loading branch information
CaseyBatten committed Aug 1, 2024
1 parent 93176e3 commit a9b16dd
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 1 deletion.
7 changes: 7 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 @@ -567,3 +567,10 @@ def open_lid(self) -> None:
dropOffset=LabwareOffsetVector(x=14, y=0, z=0),
)
)

def is_lid_on(self) -> bool:
"""Returns True if the Absorbance Reader's lid is currently on the Reader slot."""
abs_state = self._engine_client.state.modules.get_absorbance_reader_substate(
self.module_id
)
return abs_state.is_lid_on
4 changes: 4 additions & 0 deletions api/src/opentrons/protocol_api/core/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,7 @@ def close_lid(self) -> None:
@abstractmethod
def open_lid(self) -> None:
"""Open the Absorbance Reader's lid."""

@abstractmethod
def is_lid_on(self) -> bool:
"""Return True if the Absorbance Reader's lid is currently closed."""
7 changes: 6 additions & 1 deletion api/src/opentrons/protocol_api/module_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -994,9 +994,14 @@ def close_lid(self) -> None:

@requires_version(2, 21)
def open_lid(self) -> None:
"""Close the lid of the Absorbance Reader."""
"""Open the lid of the Absorbance Reader."""
self._core.open_lid()

@requires_version(2, 21)
def is_lid_on(self) -> bool:
"""Return True if the Absorbance Reader's lid is currently closed."""
return self._core.is_lid_on()

@requires_version(2, 21)
def initialize(self, wavelength: int) -> None:
"""Initialize the Absorbance Reader by taking zero reading."""
Expand Down
8 changes: 8 additions & 0 deletions api/src/opentrons/protocol_api/protocol_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
RobotTypeError,
UnsupportedAPIError,
)
from opentrons_shared_data.errors.exceptions import CommandPreconditionViolated

from ._types import OffDeckType
from .core.common import ModuleCore, LabwareCore, ProtocolCore
Expand Down Expand Up @@ -700,6 +701,13 @@ def move_labware(
f"Expected labware of type 'Labware' but got {type(labware)}."
)

# Ensure that when moving to an absorbance reader than the lid is open
if isinstance(new_location, AbsorbanceReaderContext):
if new_location.is_lid_on():
raise CommandPreconditionViolated(
f"Cannot move {labware.name} onto the Absorbance Reader Module when Lid is Closed."
)

location: Union[
ModuleCore,
LabwareCore,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
from ...errors.error_occurrence import ErrorOccurrence
from ...errors import CannotPerformModuleAction
from opentrons.protocol_engine.types import (
LabwareOffsetVector,
LabwareMovementOffsetData,
Expand All @@ -15,6 +16,8 @@
from opentrons.protocol_engine.resources import labware_validation
from .types import MoveLidResult

from opentrons.drivers.types import AbsorbanceReaderLidStatus

if TYPE_CHECKING:
from opentrons.protocol_engine.state import StateView
from opentrons.protocol_engine.execution import (
Expand Down Expand Up @@ -119,6 +122,21 @@ async def execute(
labware_definition_uri=loaded_lid.definitionUri,
labware_location=new_location,
)

if not self._state_view.config.use_virtual_modules:
abs_reader = self._equipment.get_module_hardware_api(mod_substate.module_id)

if abs_reader is not None:
result = abs_reader.lid_status
if result is not AbsorbanceReaderLidStatus.ON:
raise CannotPerformModuleAction(
"The Opentrons Plate Reader lid mechanicaly position did not match expected Closed state."
)
else:
raise CannotPerformModuleAction(
"Could not reach the Hardware API for Opentrons Plate Reader Module."
)

return SuccessData(
public=CloseLidResult(
lidId=loaded_lid.id, newLocation=new_location, offsetId=new_offset_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@

from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
from ...errors.error_occurrence import ErrorOccurrence
from ...errors import CannotPerformModuleAction
from opentrons.protocol_engine.types import (
LabwareOffsetVector,
LabwareMovementOffsetData,
)
from .types import MoveLidResult
from opentrons.protocol_engine.resources import labware_validation

from opentrons.drivers.types import AbsorbanceReaderLidStatus

if TYPE_CHECKING:
from opentrons.protocol_engine.state import StateView
from opentrons.protocol_engine.execution import (
Expand Down Expand Up @@ -104,6 +107,21 @@ async def execute(self, params: OpenLidParams) -> SuccessData[OpenLidResult, Non
labware_definition_uri=loaded_lid.definitionUri,
labware_location=new_location,
)

if not self._state_view.config.use_virtual_modules:
abs_reader = self._equipment.get_module_hardware_api(mod_substate.module_id)

if abs_reader is not None:
result = abs_reader.lid_status
if result is not AbsorbanceReaderLidStatus.OFF:
raise CannotPerformModuleAction(
"The Opentrons Plate Reader lid mechanicaly position did not match expected Open state."
)
else:
raise CannotPerformModuleAction(
"Could not reach the Hardware API for Opentrons Plate Reader Module."
)

return SuccessData(
public=OpenLidResult(
lidId=loaded_lid.id,
Expand Down
35 changes: 35 additions & 0 deletions api/src/opentrons/protocol_engine/commands/load_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

from opentrons.protocol_engine.resources import deck_configuration_provider

from opentrons.drivers.types import AbsorbanceReaderLidStatus

if TYPE_CHECKING:
from ..state import StateView
from ..execution import EquipmentHandler
Expand Down Expand Up @@ -151,6 +153,39 @@ async def execute(
module_id=params.moduleId,
)

# Handle lid position update for loaded Plate Reader module on deck
if (
not self._state_view.config.use_virtual_modules
and params.model == ModuleModel.ABSORBANCE_READER_V1
and params.moduleId is not None
):
abs_reader = self._equipment.get_module_hardware_api(
self._state_view.modules.get_absorbance_reader_substate(
params.moduleId
).module_id
)

if abs_reader is not None:
result = abs_reader.lid_status
if (
isinstance(result, AbsorbanceReaderLidStatus)
and result is not AbsorbanceReaderLidStatus.ON
):
reader_area = self._state_view.modules.ensure_and_convert_module_fixture_location(
params.location.slotName,
self._state_view.config.deck_type,
params.model,
)
lid_labware = self._state_view.labware.get_by_addressable_area(
reader_area
)
if lid_labware is not None:
lid_labware.location = (
self._state_view.modules.absorbance_reader_dock_location(
params.moduleId
)
)

return SuccessData(
public=LoadModuleResult(
moduleId=loaded_module.module_id,
Expand Down
16 changes: 16 additions & 0 deletions api/src/opentrons/protocol_engine/state/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,22 @@ def get_by_slot(

return None

def get_by_addressable_area(
self,
addressable_area: str,
) -> Optional[LoadedLabware]:
"""Get the labware located in a given addressable area, if any."""
loaded_labware = list(self._state.labware_by_id.values())

for labware in loaded_labware:
if (
isinstance(labware.location, AddressableAreaLocation)
and labware.location.addressableAreaName == addressable_area
):
return labware

return None

def get_definition(self, labware_id: str) -> LabwareDefinition:
"""Get labware definition by the labware's unique identifier."""
return self.get_definition_by_uri(
Expand Down

0 comments on commit a9b16dd

Please sign in to comment.