Skip to content

Commit

Permalink
api and robot server test fixes and engine improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
CaseyBatten committed Mar 22, 2024
1 parent 4ebcade commit ea51ac1
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 81 deletions.
2 changes: 1 addition & 1 deletion api/src/opentrons/hardware_control/modules/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def from_model(cls, model: ModuleModel) -> ModuleType:
def to_module_fixture_id(cls, module_type: ModuleType) -> str:
if module_type == ModuleType.THERMOCYCLER:
# Thermocyclers are "loaded" in B1 only
return "thermocyclerModuleFrontV2"
return "thermocyclerModuleV2Front"
if module_type == ModuleType.TEMPERATURE:
return "temperatureModuleV2"
if module_type == ModuleType.HEATER_SHAKER:
Expand Down
6 changes: 4 additions & 2 deletions api/src/opentrons/protocol_api/core/legacy/deck.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,10 +278,12 @@ def resolve_module_location(
if isinstance(location, str) or isinstance(location, int):
slot_def = self.get_slot_definition(str(location))
compatible_modules = slot_def["compatibleModuleTypes"]
cutout_fixture_id = ModuleType.to_module_fixture_id(module_type)
if module_type.value in compatible_modules:
return location
elif cutout_fixture_id == slot_def["id"]:
elif (
self._definition["robot"]["model"] == "OT-3 Standard"
and ModuleType.to_module_fixture_id(module_type) == slot_def["id"]
):
return location
else:
raise ValueError(
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/protocol_api/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ def ensure_and_convert_module_fixture_location(
slot: addressable_area
for slot, addressable_area in zip(valid_slots, addressable_areas)
}
return map_addressable_area[deck_slot.name]
return map_addressable_area[deck_slot.value]


def ensure_hold_time_seconds(
Expand Down
140 changes: 117 additions & 23 deletions api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
from decoy import Decoy

from opentrons_shared_data.deck import load as load_deck
from opentrons_shared_data.deck.dev_types import DeckDefinitionV5, SlotDefV3
from opentrons_shared_data.deck.dev_types import (
DeckDefinitionV5,
SlotDefV3,
)
from opentrons_shared_data.pipette.dev_types import PipetteNameType
from opentrons_shared_data.labware.dev_types import (
LabwareDefinition as LabwareDefDict,
Expand Down Expand Up @@ -1154,7 +1157,7 @@ def test_add_labware_definition(
EngineModuleModel.THERMOCYCLER_MODULE_V2,
ThermocyclerModuleCore,
lazy_fixture("ot3_standard_deck_def"),
DeckSlotName.SLOT_A1,
DeckSlotName.SLOT_B1,
"OT-3 Standard",
),
(
Expand Down Expand Up @@ -1193,12 +1196,22 @@ def test_load_module(
[mock_hw_mod_1, mock_hw_mod_2]
)

decoy.when(subject.get_slot_definition(slot_name)).then_return(
cast(
SlotDefV3,
{"compatibleModuleTypes": [ModuleType.from_model(requested_model)]},
if robot_type == "OT-2 Standard":
decoy.when(subject.get_slot_definition(slot_name)).then_return(
cast(
SlotDefV3,
{"compatibleModuleTypes": [ModuleType.from_model(requested_model)]},
)
)
)
else:
decoy.when(
mock_engine_client.state.addressable_areas.state.deck_definition
).then_return(deck_def)
decoy.when(
mock_engine_client.state.addressable_areas.get_cutout_id_by_deck_slot_name(
slot_name
)
).then_return("cutout" + slot_name.value)

decoy.when(mock_engine_client.state.config.robot_type).then_return(robot_type)

Expand Down Expand Up @@ -1269,14 +1282,6 @@ def test_load_module(
DeckSlotName.SLOT_D2,
"OT-3 Standard",
),
(
MagneticModuleModel.MAGNETIC_V2,
EngineModuleModel.MAGNETIC_MODULE_V2,
MagneticModuleCore,
lazy_fixture("ot3_standard_deck_def"),
DeckSlotName.SLOT_A2,
"OT-3 Standard",
),
(
ThermocyclerModuleModel.THERMOCYCLER_V1,
EngineModuleModel.THERMOCYCLER_MODULE_V1,
Expand Down Expand Up @@ -1327,9 +1332,19 @@ def test_load_module_raises_wrong_location(

decoy.when(mock_engine_client.state.config.robot_type).then_return(robot_type)

decoy.when(subject.get_slot_definition(slot_name)).then_return(
cast(SlotDefV3, {"compatibleModuleTypes": []})
)
if robot_type == "OT-2 Standard":
decoy.when(subject.get_slot_definition(slot_name)).then_return(
cast(SlotDefV3, {"compatibleModuleTypes": []})
)
else:
decoy.when(
mock_engine_client.state.addressable_areas.state.deck_definition
).then_return(deck_def)
decoy.when(
mock_engine_client.state.addressable_areas.get_cutout_id_by_deck_slot_name(
slot_name
)
).then_return("cutout" + slot_name.value)

with pytest.raises(
ValueError,
Expand All @@ -1342,6 +1357,75 @@ def test_load_module_raises_wrong_location(
)


@pytest.mark.parametrize(
(
"requested_model",
"engine_model",
"expected_core_cls",
"deck_def",
"slot_name",
"robot_type",
),
[
(
MagneticModuleModel.MAGNETIC_V2,
EngineModuleModel.MAGNETIC_MODULE_V2,
MagneticModuleCore,
lazy_fixture("ot3_standard_deck_def"),
DeckSlotName.SLOT_A2,
"OT-3 Standard",
),
],
)
def test_load_module_raises_module_fixture_id_does_not_exist(
decoy: Decoy,
mock_engine_client: EngineClient,
mock_sync_hardware_api: SyncHardwareAPI,
requested_model: ModuleModel,
engine_model: EngineModuleModel,
expected_core_cls: Type[ModuleCore],
subject: ProtocolCore,
deck_def: DeckDefinitionV5,
slot_name: DeckSlotName,
robot_type: RobotType,
) -> None:
"""It should issue a load module engine command and raise an error for unmatched fixtures."""
mock_hw_mod_1 = decoy.mock(cls=AbstractModule)
mock_hw_mod_2 = decoy.mock(cls=AbstractModule)

decoy.when(mock_hw_mod_1.device_info).then_return({"serial": "abc123"})
decoy.when(mock_hw_mod_2.device_info).then_return({"serial": "xyz789"})
decoy.when(mock_sync_hardware_api.attached_modules).then_return(
[mock_hw_mod_1, mock_hw_mod_2]
)

decoy.when(mock_engine_client.state.config.robot_type).then_return(robot_type)

if robot_type == "OT-2 Standard":
decoy.when(subject.get_slot_definition(slot_name)).then_return(
cast(SlotDefV3, {"compatibleModuleTypes": []})
)
else:
decoy.when(
mock_engine_client.state.addressable_areas.state.deck_definition
).then_return(deck_def)
decoy.when(
mock_engine_client.state.addressable_areas.get_cutout_id_by_deck_slot_name(
slot_name
)
).then_return("cutout" + slot_name.value)

with pytest.raises(
ValueError,
match=f"Module Type {ModuleType.from_model(requested_model).value} does not have a related fixture ID.",
):
subject.load_module(
model=requested_model,
deck_slot=slot_name,
configuration=None,
)


# APIv2.15 because we're expecting a fixed trash.
@pytest.mark.parametrize("api_version", [APIVersion(2, 15)])
def test_load_mag_block(
Expand All @@ -1366,6 +1450,14 @@ def test_load_mag_block(
},
)
)
decoy.when(
mock_engine_client.state.addressable_areas.state.deck_definition
).then_return(ot3_standard_deck_def)
decoy.when(
mock_engine_client.state.addressable_areas.get_cutout_id_by_deck_slot_name(
DeckSlotName.SLOT_A2
)
).then_return("cutout" + DeckSlotName.SLOT_A2.value)

decoy.when(
mock_engine_client.load_module(
Expand Down Expand Up @@ -1450,12 +1542,14 @@ def test_load_module_thermocycler_with_no_location(
decoy.when(mock_hw_mod.device_info).then_return({"serial": "xyz789"})
decoy.when(mock_sync_hardware_api.attached_modules).then_return([mock_hw_mod])
decoy.when(mock_engine_client.state.config.robot_type).then_return("OT-3 Standard")
decoy.when(subject.get_slot_definition(expected_slot)).then_return(
cast(
SlotDefV3,
{"compatibleModuleTypes": [ModuleType.from_model(requested_model)]},
decoy.when(
mock_engine_client.state.addressable_areas.state.deck_definition
).then_return(deck_def)
decoy.when(
mock_engine_client.state.addressable_areas.get_cutout_id_by_deck_slot_name(
expected_slot
)
)
).then_return("cutout" + expected_slot.value)

decoy.when(
mock_engine_client.load_module(
Expand Down
25 changes: 25 additions & 0 deletions api/tests/opentrons/protocol_engine/test_create_protocol_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from opentrons_shared_data.deck.dev_types import DeckDefinitionV5
from opentrons_shared_data.robot.dev_types import RobotType
from opentrons_shared_data.deck import load as load_deck

from opentrons.calibration_storage.helpers import uri_from_details
from opentrons.hardware_control import API as HardwareAPI
Expand All @@ -18,6 +19,30 @@
from opentrons.protocol_engine.types import DeckSlotLocation, LoadedLabware
from opentrons.types import DeckSlotName

from opentrons.protocols.api_support.deck_type import (
STANDARD_OT2_DECK,
SHORT_TRASH_DECK,
STANDARD_OT3_DECK,
)


@pytest.fixture(scope="session")
def ot2_standard_deck_def() -> DeckDefinitionV5:
"""Get the OT-2 standard deck definition."""
return load_deck(STANDARD_OT2_DECK, 5)


@pytest.fixture(scope="session")
def ot2_short_trash_deck_def() -> DeckDefinitionV5:
"""Get the OT-2 with short trash standard deck definition."""
return load_deck(SHORT_TRASH_DECK, 5)


@pytest.fixture(scope="session")
def ot3_standard_deck_def() -> DeckDefinitionV5:
"""Get the OT-2 standard deck definition."""
return load_deck(STANDARD_OT3_DECK, 5)


@pytest.mark.parametrize(
(
Expand Down
4 changes: 2 additions & 2 deletions robot-server/robot_server/persistence/_migrations/v3_to_v4.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ def migrate(self, source_dir: Path, dest_dir: Path) -> None:
"""Migrate the persistence directory from schema 3 to 4."""
for item in source_dir.iterdir():
if item.is_dir():
shutil.copytree(src=item, dst=dest_dir)
shutil.copytree(src=item, dst=dest_dir / item.name)
else:
shutil.copy(src=item, dst=dest_dir)
shutil.copy(src=item, dst=dest_dir / item.name)
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ async def _assert_reset_was_successful(
all_files_and_directories = set(persistence_directory.glob("**/*"))
expected_files_and_directories = {
persistence_directory / "robot_server.db",
persistence_directory / "3",
persistence_directory / "3" / "protocols",
persistence_directory / "3" / "robot_server.db",
persistence_directory / "4",
persistence_directory / "4" / "protocols",
persistence_directory / "4" / "robot_server.db",
}
assert all_files_and_directories == expected_files_and_directories

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,10 @@ async def test_protocol_labware_files_persist() -> None:
assert restarted_protocol_detail == protocol_detail

four_tuberack = Path(
f"{server.persistence_directory}/3/protocols/{protocol_id}/cpx_4_tuberack_100ul.json"
f"{server.persistence_directory}/4/protocols/{protocol_id}/cpx_4_tuberack_100ul.json"
)
six_tuberack = Path(
f"{server.persistence_directory}/3/protocols/{protocol_id}/cpx_6_tuberack_100ul.json"
f"{server.persistence_directory}/4/protocols/{protocol_id}/cpx_6_tuberack_100ul.json"
)
assert four_tuberack.is_file()
assert six_tuberack.is_file()
Expand Down
4 changes: 2 additions & 2 deletions shared-data/deck/types/schemaV5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export type WasteChuteCutoutFixtureId =
| 'stagingAreaSlotWithWasteChuteRightAdapterCovered'
| 'stagingAreaSlotWithWasteChuteRightAdapterNoCover'

export type FlexModuleCutoutFixtureId =
export type FlexModuleCutoutFixtureId =
| 'heaterShakerModuleV1'
| 'temperatureModuleV2'
| 'magneticBlockModuleV1'
Expand All @@ -135,6 +135,6 @@ export type CutoutFixtureId =
| StagingAreaRightSlotFixtureId
| TrashBinAdapterCutoutFixtureId
| WasteChuteCutoutFixtureId
| FlexModuleCutoutFixtureId
| FlexModuleCutoutFixtureId
| OT2SingleStandardSlot
| OT2FixedTrashSlot
Loading

0 comments on commit ea51ac1

Please sign in to comment.