Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/kynan/nbstripout
rev: 0.6.1
rev: 0.9.1
hooks:
- id: nbstripout
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.1
rev: v0.15.14
hooks:
# Run the linter.
- id: ruff
Expand Down
10 changes: 7 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ghcr.io/ad-sdl/madsci
FROM ghcr.io/ad-sdl/madsci:v0.8.0

LABEL org.opencontainers.image.source=https://github.com/AD-SDL/ot2_module
LABEL org.opencontainers.image.description="Drivers and REST API's for the Opentrons OT2 LiquidHandling robots"
Expand All @@ -13,10 +13,14 @@ RUN mkdir -p ot2_module
COPY ./src ot2_module/src
COPY ./README.md ot2_module/README.md
COPY ./pyproject.toml ot2_module/pyproject.toml
COPY ./tests ot2_module/tests

# Install into the madsci venv (system pip would land in /usr/lib site-packages,
# invisible to the venv interpreter the entrypoint actually runs).
RUN --mount=type=cache,target=/root/.cache \
pip install -e ./ot2_module
uv pip install --python ${MADSCI_VENV}/bin/python -e ./ot2_module

# Note: do not switch USER here — the base entrypoint runs userdel/useradd as
# root to remap UID/GID to the host's, then drops to the madsci user itself.

CMD ["python", "ot2_module/src/ot2_rest_node.py"]

Expand Down
2,879 changes: 1,376 additions & 1,503 deletions pdm.lock

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "ot2_module"
version = "2.0.0"
version = "2.0.1"
description = "Software for automatting an OT2 liquid_handler"
authors = [
{name = "Ryan D. Lewis", email="ryan.lewis@anl.gov"},
Expand All @@ -9,11 +9,11 @@ authors = [
{name = "Tobias Ginsburg", email = "tginsburg@anl.gov"},
]
dependencies = [
"madsci.node_module~=0.5.0",
"madsci.client~=0.5.0",
"madsci.common~=0.5.0"
"madsci.node_module~=0.8.0",
"madsci.client~=0.8.0",
"madsci.common~=0.8.0"
]
requires-python = ">=3.9.1"
requires-python = ">=3.10"
readme = "README.md"
license = {text = "MIT"}

Expand Down
1 change: 1 addition & 0 deletions scripts/delete_all_runs.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""delete all runs"""

from argparse import ArgumentParser

import requests
Expand Down
1 change: 1 addition & 0 deletions src/ot2_interface/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Stores dataclasses/args/config for the ot2 drivers"""

import json
from argparse import ArgumentParser, Namespace
from pathlib import Path
Expand Down
1 change: 1 addition & 0 deletions src/ot2_interface/ot2_driver_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class RobotStatus(Enum):
FAILED = "failed"
PAUSED = "paused"
OFFLINE = "offline"
STOPPED = "stopped"


class RunStatus(Enum):
Expand Down
1 change: 1 addition & 0 deletions src/ot2_interface/protopiler/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Dataclasses and other configuration used in the protopiler"""

from pathlib import Path
from typing import List, Literal, Optional, TypeVar, Union

Expand Down
1 change: 1 addition & 0 deletions src/ot2_interface/protopiler/deconstructor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Deconstructor, parses a protocol and turns it into a config"""

import re
import subprocess
from argparse import ArgumentParser
Expand Down
4 changes: 3 additions & 1 deletion src/ot2_interface/protopiler/protopiler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Protopiler is designed to compile a config yaml into a working protocol"""

import argparse
import copy
from datetime import datetime
Expand All @@ -7,6 +8,7 @@
from typing import Dict, Generator, List, Optional, Tuple, Union

import pandas as pd

from ot2_interface.protopiler.config import (
Clear_Pipette,
CommandBase,
Expand Down Expand Up @@ -501,7 +503,7 @@ def yaml_to_protocol(
"#location#", f'"{location}"'
)
labware_command = labware_command.replace(
"#nickname#", f'{"module"}'
"#nickname#", f"{'module'}"
)
labware_command = labware_command.replace(
"#labware_name#", f'"{name}"'
Expand Down
1 change: 1 addition & 0 deletions src/ot2_interface/protopiler/resource_manager.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Class to manage/keep track of resources used by a protocol"""

import json
import re
from argparse import ArgumentParser
Expand Down
1 change: 1 addition & 0 deletions src/ot2_interface/protopiler/test_configs/basic_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""basic config module"""

from opentrons import protocol_api

metadata = {
Expand Down
51 changes: 25 additions & 26 deletions src/ot2_rest_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@
from madsci.common.types.resource_types import Container, Pool, Slot, Stack
from madsci.node_module.helpers import action
from madsci.node_module.rest_node_module import RestNode
from ot2_interface.ot2_driver_http import OT2_Config, OT2_Driver
from typing_extensions import Annotated

from ot2_interface.ot2_driver_http import OT2_Config, OT2_Driver


class OT2NodeConfig(RestNodeConfig):
"""Configuration for the OT2 node module."""

ot2_ip: Optional[str] = None
"ip of opentrons device"
rate_limit_requests: int = 500
"max number of requests per minute to the ot2 device"


class OT2Node(RestNode):
Expand All @@ -33,27 +36,26 @@ def startup_handler(self) -> None:
temp_dir = Path.home() / ".madsci" / ".ot2_temp"
temp_dir.mkdir(exist_ok=True)
self.protocols_folder_path = str(
temp_dir / self.node_definition.node_name / "protocols/"
temp_dir / self.node_info.node_name / "protocols/"
)
# Create templates
self._create_ot2_templates()

# Create deck instance
self.deck = self.resource_client.create_resource_from_template(
template_name="ot2_deck",
resource_name=f"ot2_{self.node_definition.node_name}_deck",
resource_name=f"{self.node_info.node_name}.deck",
add_to_database=True,
)

# Create 12 deck slots (1-11 standard, 12 is trash)
for i in range(1, 13):
slot_name = f"ot2_{self.node_definition.node_name}_deck_slot_{i}"
slot_name = f"{self.node_info.node_name}.deck.nest_{i}"
template_name = "ot2_trash_slot" if i == 12 else "ot2_deck_slot"

slot = self.resource_client.create_resource_from_template(
template_name=template_name,
resource_name=slot_name,
add_to_database=True,
)

try:
Expand All @@ -66,8 +68,7 @@ def startup_handler(self) -> None:
for mount in ["left", "right"]:
mount_slot = self.resource_client.create_resource_from_template(
template_name="ot2_pipette_mount",
resource_name=f"ot2_{self.node_definition.node_name}_{mount}_mount",
add_to_database=True,
resource_name=f"{self.node_info.node_name}.{mount}_mount",
)
self.pipette_slots[mount] = mount_slot

Expand All @@ -93,7 +94,7 @@ def _create_ot2_templates(self) -> None:
capacity=12,
attributes={
"deck_type": "OT2",
"slot_count": 12,
"nest_count": 12,
"sbs_compatible": True,
"description": "OT2 deck with 11 standard slots plus trash bin",
},
Expand All @@ -105,7 +106,7 @@ def _create_ot2_templates(self) -> None:
description="Template for OT2 deck container. Holds 11 deck slots plus trash bin.",
required_overrides=["resource_name"],
tags=["ot2", "deck", "container"],
created_by=self.node_definition.node_id,
created_by=self.node_info.node_id,
version="1.0.0",
)

Expand All @@ -127,7 +128,7 @@ def _create_ot2_templates(self) -> None:
description="Template for OT2 deck slot. Standard SBS-compatible position.",
required_overrides=["resource_name"],
tags=["ot2", "deck", "slot"],
created_by=self.node_definition.node_id,
created_by=self.node_info.node_id,
version="1.0.0",
)

Expand All @@ -148,7 +149,7 @@ def _create_ot2_templates(self) -> None:
description="Template for OT2 trash bin slot.",
required_overrides=["resource_name"],
tags=["ot2", "trash", "stack"],
created_by=self.node_definition.node_id,
created_by=self.node_info.node_id,
version="1.0.0",
)

Expand All @@ -169,7 +170,7 @@ def _create_ot2_templates(self) -> None:
description="Template for OT2 pipette mount slot.",
required_overrides=["resource_name"],
tags=["ot2", "pipette", "mount", "slot"],
created_by=self.node_definition.node_id,
created_by=self.node_info.node_id,
version="1.0.0",
)

Expand All @@ -196,7 +197,7 @@ def _create_ot2_templates(self) -> None:
description="Template for OT2 P20 Single-Channel pipette (1-20 µL).",
required_overrides=["resource_name"],
tags=["ot2", "pipette", "p20", "single-channel", "pool"],
created_by=self.node_definition.node_id,
created_by=self.node_info.node_id,
version="1.0.0",
)

Expand Down Expand Up @@ -229,7 +230,7 @@ def _create_ot2_templates(self) -> None:
description="Template for OT2 P300 Single-Channel pipette (20-300 µL).",
required_overrides=["resource_name"],
tags=["ot2", "pipette", "p300", "single-channel", "pool"],
created_by=self.node_definition.node_id,
created_by=self.node_info.node_id,
version="1.0.0",
)

Expand Down Expand Up @@ -265,7 +266,7 @@ def _create_ot2_templates(self) -> None:
description="Template for OT2 P1000 Single-Channel pipette (100-1000 µL).",
required_overrides=["resource_name"],
tags=["ot2", "pipette", "p1000", "single-channel", "pool"],
created_by=self.node_definition.node_id,
created_by=self.node_info.node_id,
version="1.0.0",
)

Expand Down Expand Up @@ -296,7 +297,7 @@ def _create_ot2_templates(self) -> None:
description="Template for OT2 P20 8-Channel pipette (1-20 µL).",
required_overrides=["resource_name"],
tags=["ot2", "pipette", "p20", "8-channel", "multi-channel", "pool"],
created_by=self.node_definition.node_id,
created_by=self.node_info.node_id,
version="1.0.0",
)

Expand Down Expand Up @@ -333,7 +334,7 @@ def _create_ot2_templates(self) -> None:
description="Template for OT2 P300 8-Channel pipette (20-300 µL).",
required_overrides=["resource_name"],
tags=["ot2", "pipette", "p300", "8-channel", "multi-channel", "pool"],
created_by=self.node_definition.node_id,
created_by=self.node_info.node_id,
version="1.0.0",
)

Expand Down Expand Up @@ -429,9 +430,7 @@ def execute(self, protocol_path, payload=None, resource_config=None):
try:
protocol_id, run_id = self.ot2_interface.transfer(protocol_file_path)
self.logger.log(
"OT2 "
+ self.node_definition.node_name
+ " protocol transfer successful"
"OT2 " + self.node_info.node_name + " protocol transfer successful"
)

self.run_id = run_id
Expand All @@ -442,39 +441,39 @@ def execute(self, protocol_path, payload=None, resource_config=None):
# poll_OT2_until_run_completion()
self.logger.log(
"OT2 "
+ self.node_definition.node_name
+ self.node_info.node_name
+ " succeeded in executing a protocol"
)
response_msg = (
"OT2 "
+ self.node_definition.node_name
+ self.node_info.node_name
+ " successfully IDLE running a protocol"
)
return "succeeded", response_msg, run_id

elif resp["data"]["status"] == "stopped":
self.logger.log(
"OT2 "
+ self.node_definition.node_name
+ self.node_info.node_name
+ " stopped while executing a protocol"
)
response_msg = (
"OT2 "
+ self.node_definition.node_name
+ self.node_info.node_name
+ " successfully IDLE after stopping a protocol"
)
return "stopped", response_msg, run_id

else:
self.logger.log(
"OT2 "
+ self.node_definition.node_name
+ self.node_info.node_name
+ " failed in executing a protocol"
)
self.logger.log(resp["data"])
response_msg = (
"OT2 "
+ self.node_definition.node_name
+ self.node_info.node_name
+ " failed running a protocol\n"
+ str(resp["data"])
)
Expand Down
Loading