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

chore(api): port absorbance reader commands to state update #17113

Merged
merged 31 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9e97dc6
WIP read data command in state update
TamarZanzouri Dec 11, 2024
f97b320
refactor current set lid status and use handler for absorbanse reader
TamarZanzouri Dec 12, 2024
8ad1ec2
added module_state_update prop
TamarZanzouri Dec 12, 2024
5082f97
initialize reader
TamarZanzouri Dec 12, 2024
ba68978
initialize reader code complete - not tested yet
TamarZanzouri Dec 13, 2024
c476b6b
removed redundant module_id from tests
TamarZanzouri Dec 16, 2024
2baefdf
merged module_state_update with absorbance props
TamarZanzouri Dec 16, 2024
92fa9b4
WIP adding missing tests to absorbance reader
TamarZanzouri Dec 16, 2024
efd2eba
added missing tests for initialize - need to fix lint and add raising…
TamarZanzouri Dec 18, 2024
238f675
WIP read missing tests
TamarZanzouri Dec 19, 2024
3cf89ba
read simple test
TamarZanzouri Dec 19, 2024
285c71a
WIP open lid test
TamarZanzouri Dec 20, 2024
d07b6ee
WIP open lid lint fixes - need to fix state
TamarZanzouri Dec 20, 2024
db386d0
working basic test for open lid
TamarZanzouri Dec 20, 2024
773174a
test close lid - basic test complete
TamarZanzouri Dec 20, 2024
66fac26
raise test wip
TamarZanzouri Dec 20, 2024
73b249e
added tests for raising
TamarZanzouri Dec 23, 2024
6d37f1f
added tests for raising open lid
TamarZanzouri Dec 23, 2024
192e39d
added raising tests to all files. read WIP
TamarZanzouri Dec 23, 2024
efa879a
read raises
TamarZanzouri Dec 23, 2024
a1fe6a5
removed type ignore
TamarZanzouri Dec 23, 2024
0bafe4b
linting and module_id fix
TamarZanzouri Dec 23, 2024
17720ec
Update api/src/opentrons/protocol_engine/commands/absorbance_reader/c…
TamarZanzouri Dec 23, 2024
7db236e
linting
TamarZanzouri Dec 23, 2024
091c9b1
removed comments
TamarZanzouri Dec 23, 2024
c5e10fd
Merge branch 'edge' into EXEC-756-port-module-store-updates
TamarZanzouri Dec 23, 2024
9c6e049
unused ignore after setup
TamarZanzouri Dec 24, 2024
716ec08
model_construct with type ignore
TamarZanzouri Dec 24, 2024
035c57c
Update api/tests/opentrons/protocol_engine/commands/absorbance_reader…
TamarZanzouri Jan 2, 2025
b2569ac
change arg type to absorbance_reader_state_update
TamarZanzouri Jan 2, 2025
33c6dc9
assert module state is not set
TamarZanzouri Jan 2, 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
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)

Comment on lines +194 to +195
Copy link
Contributor

Choose a reason for hiding this comment

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

This logic looks solid down through it's usage. We utilize the files added data at the end of a run on the client as when looking through the run result to populate the list of files available for download via the app after the fact, so this should maintain that logic.

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
114 changes: 50 additions & 64 deletions api/src/opentrons/protocol_engine/state/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
heater_shaker,
temperature_module,
thermocycler,
absorbance_reader,
)
from ..actions import (
Action,
Expand Down Expand Up @@ -296,40 +295,13 @@ 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.module_state_update != update_types.NO_CHANGE
and state_update.module_state_update.module_type == "absorbanceReaderType"
):
self._handle_absorbance_reader_commands(
state_update, state_update.module_state_update.module_id
)

def _add_module_substate(
Expand Down Expand Up @@ -589,47 +561,61 @@ def _handle_thermocycler_module_commands(
)

def _handle_absorbance_reader_commands(
TamarZanzouri marked this conversation as resolved.
Show resolved Hide resolved
self,
command: Union[
absorbance_reader.Initialize,
absorbance_reader.ReadAbsorbance,
],
self, state_update: update_types.StateUpdate, module_id: str
) -> None:
module_id = command.params.moduleId
# 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."

# 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 (
state_update.module_state_update != update_types.NO_CHANGE
and state_update.module_state_update.absorbance_reader_lid
!= update_types.NO_CHANGE
):
is_lid_on = state_update.module_state_update.absorbance_reader_lid.is_lid_on
elif (
state_update.module_state_update != update_types.NO_CHANGE
and state_update.module_state_update.initialize_absorbance_reader_update
!= update_types.NO_CHANGE
):
module_id = AbsorbanceReaderId(module_id)
configured = True
measured = False
is_lid_on = is_lid_on
measure_mode = AbsorbanceReaderMeasureMode(
state_update.module_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 = (
state_update.module_state_update.initialize_absorbance_reader_update.sample_wave_lengths
)
reference_wavelength = (
state_update.module_state_update.initialize_absorbance_reader_update.reference_wave_length
)
data = None
elif (
state_update.module_state_update != update_types.NO_CHANGE
and state_update.module_state_update.absorbance_reader_data
!= update_types.NO_CHANGE
):
data = state_update.module_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 @@ -14,9 +14,11 @@
TipGeometry,
AspiratedFluid,
LiquidClassRecord,
ABSMeasureMode,
)
from opentrons.types import MountType
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
from opentrons_shared_data.module.types import ModuleType
from opentrons_shared_data.pipette.types import PipetteNameType


Expand Down Expand Up @@ -249,10 +251,38 @@ 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 ModuleStateUpdate:
"""An update to the module state."""

module_id: str
module_type: ModuleType
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 @@ -309,7 +339,7 @@ class StateUpdate:

liquid_operated: LiquidOperatedUpdate | NoChangeType = NO_CHANGE

absorbance_reader_lid: AbsorbanceReaderLidUpdate | NoChangeType = NO_CHANGE
module_state_update: ModuleStateUpdate | NoChangeType = NO_CHANGE

liquid_class_loaded: LiquidClassLoadedUpdate | NoChangeType = NO_CHANGE

Expand Down Expand Up @@ -573,8 +603,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
self.module_state_update = ModuleStateUpdate(
module_id=module_id,
module_type="absorbanceReaderType",
absorbance_reader_lid=AbsorbanceReaderLidUpdate(is_lid_on=is_lid_on),
TamarZanzouri marked this conversation as resolved.
Show resolved Hide resolved
)
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`."""
self.module_state_update = ModuleStateUpdate(
module_id=module_id,
module_type="absorbanceReaderType",
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."""
self.module_state_update = ModuleStateUpdate(
module_id=module_id,
module_type="absorbanceReaderType",
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 Magnetic Module commands."""
TamarZanzouri marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading