Skip to content

Commit

Permalink
chore(api): port absorbance reader commands to state update (#17113)
Browse files Browse the repository at this point in the history
  • Loading branch information
TamarZanzouri authored Jan 2, 2025
1 parent 5e9955b commit 576501a
Show file tree
Hide file tree
Showing 12 changed files with 1,033 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ async def execute(self, params: CloseLidParams) -> SuccessData[CloseLidResult]:
hardware_lid_status = AbsorbanceReaderLidStatus.OFF
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:
hardware_lid_status = await abs_reader.get_current_lid_status()
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
from ...errors.error_occurrence import ErrorOccurrence
from ...errors import InvalidWavelengthError
from ...state import update_types

if TYPE_CHECKING:
from opentrons.protocol_engine.state.state import StateView
Expand Down Expand Up @@ -60,6 +61,7 @@ def __init__(

async def execute(self, params: InitializeParams) -> SuccessData[InitializeResult]:
"""Initiate a single absorbance measurement."""
state_update = update_types.StateUpdate()
abs_reader_substate = self._state_view.modules.get_absorbance_reader_substate(
module_id=params.moduleId
)
Expand Down Expand Up @@ -120,10 +122,15 @@ async def execute(self, params: InitializeParams) -> SuccessData[InitializeResul
reference_wavelength=params.referenceWavelength,
)

return SuccessData(
public=InitializeResult(),
state_update.initialize_absorbance_reader(
abs_reader_substate.module_id,
params.measureMode,
params.sampleWavelengths,
params.referenceWavelength,
)

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


class Initialize(BaseCommand[InitializeParams, InitializeResult, ErrorOccurrence]):
"""A command to initialize an Absorbance Reader."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ async def execute( # noqa: C901
self, params: ReadAbsorbanceParams
) -> SuccessData[ReadAbsorbanceResult]:
"""Initiate an absorbance measurement."""
state_update = update_types.StateUpdate()
abs_reader_substate = self._state_view.modules.get_absorbance_reader_substate(
module_id=params.moduleId
)
Expand Down Expand Up @@ -149,6 +150,9 @@ async def execute( # noqa: C901
"Plate Reader data cannot be requested with a module that has not been initialized."
)

state_update.set_absorbance_reader_data(
module_id=abs_reader_substate.module_id, read_result=asbsorbance_result
)
# TODO (cb, 10-17-2024): FILE PROVIDER - Some day we may want to break the file provider behavior into a seperate API function.
# When this happens, we probably will to have the change the command results handler we utilize to track file IDs in engine.
# Today, the action handler for the FileStore looks for a ReadAbsorbanceResult command action, this will need to be delinked.
Expand Down Expand Up @@ -181,18 +185,20 @@ async def execute( # noqa: C901
# Return success data to api
return SuccessData(
public=ReadAbsorbanceResult(
data=asbsorbance_result, fileIds=file_ids
data=asbsorbance_result,
fileIds=file_ids,
),
state_update=state_update,
)

state_update.files_added = update_types.FilesAddedUpdate(file_ids=file_ids)

return SuccessData(
public=ReadAbsorbanceResult(
data=asbsorbance_result,
fileIds=file_ids,
),
state_update=update_types.StateUpdate(
files_added=update_types.FilesAddedUpdate(file_ids=file_ids)
),
state_update=state_update,
)


Expand Down
109 changes: 45 additions & 64 deletions api/src/opentrons/protocol_engine/state/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
AbsorbanceReaderMeasureMode,
)
from opentrons.types import DeckSlotName, MountType, StagingSlotName
from .update_types import AbsorbanceReaderStateUpdate
from ..errors import ModuleNotConnectedError

from ..types import (
Expand Down Expand Up @@ -63,7 +64,6 @@
heater_shaker,
temperature_module,
thermocycler,
absorbance_reader,
)
from ..actions import (
Action,
Expand Down Expand Up @@ -296,40 +296,10 @@ def _handle_command(self, command: Command) -> None:
):
self._handle_thermocycler_module_commands(command)

if isinstance(
command.result,
(
absorbance_reader.InitializeResult,
absorbance_reader.ReadAbsorbanceResult,
),
):
self._handle_absorbance_reader_commands(command)

def _handle_state_update(self, state_update: update_types.StateUpdate) -> None:
if state_update.absorbance_reader_lid != update_types.NO_CHANGE:
module_id = state_update.absorbance_reader_lid.module_id
is_lid_on = state_update.absorbance_reader_lid.is_lid_on

# Get current values:
absorbance_reader_substate = self._state.substate_by_module_id[module_id]
assert isinstance(
absorbance_reader_substate, AbsorbanceReaderSubState
), f"{module_id} is not an absorbance plate reader."
configured = absorbance_reader_substate.configured
measure_mode = absorbance_reader_substate.measure_mode
configured_wavelengths = absorbance_reader_substate.configured_wavelengths
reference_wavelength = absorbance_reader_substate.reference_wavelength
data = absorbance_reader_substate.data

self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
module_id=AbsorbanceReaderId(module_id),
configured=configured,
measured=True,
is_lid_on=is_lid_on,
measure_mode=measure_mode,
configured_wavelengths=configured_wavelengths,
reference_wavelength=reference_wavelength,
data=data,
if state_update.absorbance_reader_state_update != update_types.NO_CHANGE:
self._handle_absorbance_reader_commands(
state_update.absorbance_reader_state_update
)

def _add_module_substate(
Expand Down Expand Up @@ -589,47 +559,58 @@ def _handle_thermocycler_module_commands(
)

def _handle_absorbance_reader_commands(
self,
command: Union[
absorbance_reader.Initialize,
absorbance_reader.ReadAbsorbance,
],
self, absorbance_reader_state_update: AbsorbanceReaderStateUpdate
) -> None:
module_id = command.params.moduleId
# Get current values:
module_id = absorbance_reader_state_update.module_id
absorbance_reader_substate = self._state.substate_by_module_id[module_id]
assert isinstance(
absorbance_reader_substate, AbsorbanceReaderSubState
), f"{module_id} is not an absorbance plate reader."

# Get current values
is_lid_on = absorbance_reader_substate.is_lid_on
measured = True
configured = absorbance_reader_substate.configured
measure_mode = absorbance_reader_substate.measure_mode
configured_wavelengths = absorbance_reader_substate.configured_wavelengths
reference_wavelength = absorbance_reader_substate.reference_wavelength
is_lid_on = absorbance_reader_substate.is_lid_on

if isinstance(command.result, absorbance_reader.InitializeResult):
self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
module_id=AbsorbanceReaderId(module_id),
configured=True,
measured=False,
is_lid_on=is_lid_on,
measure_mode=AbsorbanceReaderMeasureMode(command.params.measureMode),
configured_wavelengths=command.params.sampleWavelengths,
reference_wavelength=command.params.referenceWavelength,
data=None,
data = absorbance_reader_substate.data
if (
absorbance_reader_state_update.absorbance_reader_lid
!= update_types.NO_CHANGE
):
is_lid_on = absorbance_reader_state_update.absorbance_reader_lid.is_lid_on
elif (
absorbance_reader_state_update.initialize_absorbance_reader_update
!= update_types.NO_CHANGE
):
configured = True
measured = False
is_lid_on = is_lid_on
measure_mode = AbsorbanceReaderMeasureMode(
absorbance_reader_state_update.initialize_absorbance_reader_update.measure_mode
)
elif isinstance(command.result, absorbance_reader.ReadAbsorbanceResult):
self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
module_id=AbsorbanceReaderId(module_id),
configured=configured,
measured=True,
is_lid_on=is_lid_on,
measure_mode=measure_mode,
configured_wavelengths=configured_wavelengths,
reference_wavelength=reference_wavelength,
data=command.result.data,
configured_wavelengths = (
absorbance_reader_state_update.initialize_absorbance_reader_update.sample_wave_lengths
)
reference_wavelength = (
absorbance_reader_state_update.initialize_absorbance_reader_update.reference_wave_length
)
data = None
elif (
absorbance_reader_state_update.absorbance_reader_data
!= update_types.NO_CHANGE
):
data = absorbance_reader_state_update.absorbance_reader_data.read_result
self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
module_id=AbsorbanceReaderId(module_id),
configured=configured,
measured=measured,
is_lid_on=is_lid_on,
measure_mode=measure_mode,
configured_wavelengths=configured_wavelengths,
reference_wavelength=reference_wavelength,
data=data,
)


class ModuleView:
Expand Down
70 changes: 66 additions & 4 deletions api/src/opentrons/protocol_engine/state/update_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
TipGeometry,
AspiratedFluid,
LiquidClassRecord,
ABSMeasureMode,
)
from opentrons.types import MountType
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
Expand Down Expand Up @@ -284,10 +285,37 @@ class PipetteEmptyFluidUpdate:
class AbsorbanceReaderLidUpdate:
"""An update to an absorbance reader's lid location."""

module_id: str
is_lid_on: bool


@dataclasses.dataclass
class AbsorbanceReaderDataUpdate:
"""An update to an absorbance reader's lid location."""

read_result: typing.Dict[int, typing.Dict[str, float]]


@dataclasses.dataclass(frozen=True)
class AbsorbanceReaderInitializeUpdate:
"""An update to an absorbance reader's initialization."""

measure_mode: ABSMeasureMode
sample_wave_lengths: typing.List[int]
reference_wave_length: typing.Optional[int]


@dataclasses.dataclass
class AbsorbanceReaderStateUpdate:
"""An update to the absorbance reader module state."""

module_id: str
absorbance_reader_lid: AbsorbanceReaderLidUpdate | NoChangeType = NO_CHANGE
absorbance_reader_data: AbsorbanceReaderDataUpdate | NoChangeType = NO_CHANGE
initialize_absorbance_reader_update: AbsorbanceReaderInitializeUpdate | NoChangeType = (
NO_CHANGE
)


@dataclasses.dataclass
class LiquidClassLoadedUpdate:
"""The state update from loading a liquid class."""
Expand Down Expand Up @@ -348,7 +376,9 @@ class StateUpdate:

liquid_operated: LiquidOperatedUpdate | NoChangeType = NO_CHANGE

absorbance_reader_lid: AbsorbanceReaderLidUpdate | NoChangeType = NO_CHANGE
absorbance_reader_state_update: AbsorbanceReaderStateUpdate | NoChangeType = (
NO_CHANGE
)

liquid_class_loaded: LiquidClassLoadedUpdate | NoChangeType = NO_CHANGE

Expand Down Expand Up @@ -644,8 +674,40 @@ def set_fluid_empty(self: Self, pipette_id: str) -> Self:

def set_absorbance_reader_lid(self: Self, module_id: str, is_lid_on: bool) -> Self:
"""Update an absorbance reader's lid location. See `AbsorbanceReaderLidUpdate`."""
self.absorbance_reader_lid = AbsorbanceReaderLidUpdate(
module_id=module_id, is_lid_on=is_lid_on
assert self.absorbance_reader_state_update == NO_CHANGE
self.absorbance_reader_state_update = AbsorbanceReaderStateUpdate(
module_id=module_id,
absorbance_reader_lid=AbsorbanceReaderLidUpdate(is_lid_on=is_lid_on),
)
return self

def set_absorbance_reader_data(
self, module_id: str, read_result: typing.Dict[int, typing.Dict[str, float]]
) -> Self:
"""Update an absorbance reader's read data. See `AbsorbanceReaderReadDataUpdate`."""
assert self.absorbance_reader_state_update == NO_CHANGE
self.absorbance_reader_state_update = AbsorbanceReaderStateUpdate(
module_id=module_id,
absorbance_reader_data=AbsorbanceReaderDataUpdate(read_result=read_result),
)
return self

def initialize_absorbance_reader(
self,
module_id: str,
measure_mode: ABSMeasureMode,
sample_wave_lengths: typing.List[int],
reference_wave_length: typing.Optional[int],
) -> Self:
"""Initialize absorbance reader."""
assert self.absorbance_reader_state_update == NO_CHANGE
self.absorbance_reader_state_update = AbsorbanceReaderStateUpdate(
module_id=module_id,
initialize_absorbance_reader_update=AbsorbanceReaderInitializeUpdate(
measure_mode=measure_mode,
sample_wave_lengths=sample_wave_lengths,
reference_wave_length=reference_wave_length,
),
)
return self

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for absorbance reader commands."""
Loading

0 comments on commit 576501a

Please sign in to comment.