Skip to content

Commit

Permalink
Merge branch 'edge' into make_push
Browse files Browse the repository at this point in the history
  • Loading branch information
rclarke0 authored Nov 21, 2024
2 parents f0074cb + da74897 commit 3e0390e
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ def run(
ip_json_file = os.path.join(storage_directory, "IPs.json")
try:
ip_file = json.load(open(ip_json_file))
robot_dict = ip_file.get("ip_address_list")
except FileNotFoundError:
print(f"Add .json file with robot IPs to: {storage_directory}.")
sys.exit()
Expand All @@ -294,7 +295,7 @@ def run(
ip_or_all = input("IP Address or ALL: ")
calibration_data = []
if ip_or_all.upper() == "ALL":
ip_address_list = ip_file["ip_address_list"]
ip_address_list = list(robot_dict.keys())
for ip in ip_address_list:
saved_file_path, calibration = read_robot_logs.get_calibration_offsets(
ip, storage_directory
Expand Down
3 changes: 2 additions & 1 deletion abr-testing/abr_testing/data_collection/get_run_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,11 @@ def get_all_run_logs(
ip_json_file = os.path.join(storage_directory, "IPs.json")
try:
ip_file = json.load(open(ip_json_file))
robot_dict = ip_file.get("ip_address_list")
except FileNotFoundError:
print(f"Add .json file with robot IPs to: {storage_directory}.")
sys.exit()
ip_address_list = ip_file["ip_address_list"]
ip_address_list = list(robot_dict.keys())
runs_from_storage = read_robot_logs.get_run_ids_from_google_drive(google_drive)
for ip in ip_address_list:
runs = get_run_ids_from_robot(ip)
Expand Down
52 changes: 38 additions & 14 deletions analyses-snapshot-testing/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ CACHEBUST ?= $(shell date +%s)
ANALYSIS_REF ?= edge
PROTOCOL_NAMES ?= all
OVERRIDE_PROTOCOL_NAMES ?= all
OPENTRONS_VERSION ?= edge
LOCAL_IMAGE_TAG ?= local
ANALYZER_IMAGE_NAME ?= opentrons-analysis

export OPENTRONS_VERSION # used for server
export ANALYSIS_REF # used for analysis and snapshot test
export PROTOCOL_NAMES # used for the snapshot test
export OVERRIDE_PROTOCOL_NAMES # used for the snapshot test
export ANALYSIS_REF # tag, branch or commit for the opentrons repository. Used as the image tag for the analyzer image
export PROTOCOL_NAMES # tell the test which protocols to run
export OVERRIDE_PROTOCOL_NAMES # tell the test which override protocols to run

ifeq ($(CI), true)
PYTHON=python
Expand Down Expand Up @@ -93,23 +93,47 @@ build-base-image:

.PHONY: build-opentrons-analysis
build-opentrons-analysis:
@echo "Building docker image for $(ANALYSIS_REF)"
@echo "The image will be named opentrons-analysis:$(ANALYSIS_REF)"
@echo "If you want to build a different version, run 'make build-opentrons-analysis ANALYSIS_REF=<version>'"
docker build --build-arg BASE_IMAGE_NAME=$(BASE_IMAGE_NAME) --build-arg ANALYSIS_REF=$(ANALYSIS_REF) --build-arg CACHEBUST=$(CACHEBUST) -t opentrons-analysis:$(ANALYSIS_REF) -f citools/Dockerfile.analyze citools/.
@echo "Building docker image for opentrons repository reference$(ANALYSIS_REF)"
@echo "The image will be named $(ANALYZER_IMAGE_NAME):$(ANALYSIS_REF)"
@echo "If you want to build a different version, run 'make build-opentrons-analysis ANALYSIS_REF=<tag, branch, or commit>'"
docker build --build-arg BASE_IMAGE_NAME=$(BASE_IMAGE_NAME) --build-arg ANALYSIS_REF=$(ANALYSIS_REF) --build-arg CACHEBUST=$(CACHEBUST) -t $(ANALYZER_IMAGE_NAME):$(ANALYSIS_REF) -f citools/Dockerfile.analyze citools/.

.PHONY: local-build
local-build:
.PHONY: build-local
build-local:
@echo "Building docker image for your local opentrons code"
@echo "The image will be named opentrons-analysis:local"
@echo "For a fresh build, run 'make local-build NO_CACHE=1'"
docker build --build-arg BASE_IMAGE_NAME=$(BASE_IMAGE_NAME) $(BUILD_FLAGS) -t opentrons-analysis:local -f citools/Dockerfile.local .. || true
@echo "This image will be named $(ANALYZER_IMAGE_NAME):$(LOCAL_IMAGE_TAG)"
docker build --build-arg BASE_IMAGE_NAME=$(BASE_IMAGE_NAME) -t $(ANALYZER_IMAGE_NAME):$(LOCAL_IMAGE_TAG) -f citools/Dockerfile.local ..
@echo "Build complete"

.PHONY: snapshot-test-local
snapshot-test-local: ANALYSIS_REF=$(LOCAL_IMAGE_TAG)
snapshot-test-local: build-base-image build-local
@echo "This target is overriding the ANALYSIS_REF to the LOCAL_IMAGE_TAG: $(LOCAL_IMAGE_TAG)"
@echo "ANALYSIS_REF is $(ANALYSIS_REF). The the test maps this env variable to the image tag."
@echo "The image the test will use is $(ANALYZER_IMAGE_NAME):$(LOCAL_IMAGE_TAG)"
@echo "PROTOCOL_NAMES is $(PROTOCOL_NAMES)"
@echo "OVERRIDE_PROTOCOL_NAMES is $(OVERRIDE_PROTOCOL_NAMES)"
$(PYTHON) -m pipenv run pytest -k analyses_snapshot_test -vv

.PHONY: snapshot-test-update-local
snapshot-test-update-local: ANALYSIS_REF=$(LOCAL_IMAGE_TAG)
snapshot-test-update-local: build-base-image build-local
@echo "This target is overriding the ANALYSIS_REF to the LOCAL_IMAGE_TAG: $(LOCAL_IMAGE_TAG)"
@echo "ANALYSIS_REF is $(ANALYSIS_REF). The the test maps this env variable to the image tag."
@echo "The image the test will use is $(ANALYZER_IMAGE_NAME):$(LOCAL_IMAGE_TAG)"
@echo "PROTOCOL_NAMES is $(PROTOCOL_NAMES)"
@echo "OVERRIDE_PROTOCOL_NAMES is $(OVERRIDE_PROTOCOL_NAMES)"
$(PYTHON) -m pipenv run pytest -k analyses_snapshot_test --snapshot-update

.PHONY: generate-protocols
generate-protocols:
$(PYTHON) -m pipenv run python -m automation.data.protocol_registry

# Tools for running the robot server in a container

OPENTRONS_VERSION ?= edge
export OPENTRONS_VERSION # used for the robot server image as the tag, branch or commit for the opentrons repository

.PHONY: build-rs
build-rs:
@echo "Building docker image for opentrons-robot-server:$(OPENTRONS_VERSION)"
Expand Down
17 changes: 12 additions & 5 deletions analyses-snapshot-testing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

1. Follow the instructions in [DEV_SETUP.md](../DEV_SETUP.md)
1. `cd analyses-snapshot-testing`
1. use pyenv to install python 3.12 and set it as the local python version for this directory
1. use pyenv to install python 3.13 and set it as the local python version for this directory
1. `make setup`
1. Have docker installed and ready

Expand Down Expand Up @@ -72,10 +72,17 @@ cd analyses-snapshot-testing \

> This copies in your local code to the container and runs the analyses battery against it.

1. `make build-base-image`
1. `make build-local`
1. `make local-snapshot-test`
`cd PYENV_ROOT && git pull` - make sure pyenv is up to date so you may install python 3.13.0
`pyenv install 3.13.0` - install python 3.13.0
`cd <OPENTRONS_REPO_ROOT>/analyses-snapshot-testing` - navigate to the analyses-snapshot-testing directory
`pyenv local 3.13.0` - set the local python version to 3.13.0
`make setup` - install the requirements
`make snapshot-test-local` - this target builds the base image, builds the local code into the base image, then runs the analyses battery against the image you just created

You have the option to specify one or many protocols to run the analyses on. This is also described above [Running the tests against specific protocols](#running-the-tests-against-specific-protocols)

- `make local-snapshot-test PROTOCOL_NAMES=Flex_S_v2_19_Illumina_DNA_PCR_Free OVERRIDE_PROTOCOL_NAMES=none`
- `make snapshot-test-local PROTOCOL_NAMES=Flex_S_v2_19_Illumina_DNA_PCR_Free OVERRIDE_PROTOCOL_NAMES=none`

### Updating the snapshots locally

- `make snapshot-test-update-local` - this target builds the base image, builds the local code into the base image, then runs the analyses battery against the image you just created, updating the snapshots by passing the `--update-snapshots` flag to the test
4 changes: 4 additions & 0 deletions api/docs/v2/versioning.rst
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ This table lists the correspondence between Protocol API versions and robot soft
Changes in API Versions
=======================

Version 2.21
------------
- :ref:`Liquid presence detection <lpd>` now only checks on the first aspiration of the :py:meth:`.mix` cycle.

Version 2.20
------------

Expand Down
2 changes: 2 additions & 0 deletions api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,8 @@ def mix(
``pipette.mix(1, location=wellplate['A1'])`` is a valid call, but
``pipette.mix(1, wellplate['A1'])`` is not.
.. versionchanged:: 2.21
Does not repeatedly check for liquid presence.
"""
_log.debug(
"mixing {}uL with {} repetitions in {} at rate={}".format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
test_z_axis,
test_x_axis,
test_l_axis,
test_door_switch,
test_estop,
)


Expand All @@ -20,6 +22,8 @@ class TestSection(enum.Enum):
Z_AXIS = "Z_AXIS"
L_AXIS = "L_AXIS"
X_AXIS = "X_AXIS"
DOOR_SWITCH = "DOOR_SWITCH"
ESTOP = "ESTOP"


@dataclass
Expand Down Expand Up @@ -47,6 +51,14 @@ class TestConfig:
TestSection.X_AXIS,
test_x_axis.run,
),
(
TestSection.DOOR_SWITCH,
test_door_switch.run,
),
(
TestSection.ESTOP,
test_estop.run,
),
]


Expand All @@ -71,5 +83,13 @@ def build_report(test_name: str) -> CSVReport:
title=TestSection.X_AXIS.value,
lines=test_x_axis.build_csv_lines(),
),
CSVSection(
title=TestSection.DOOR_SWITCH.value,
lines=test_door_switch.build_csv_lines(),
),
CSVSection(
title=TestSection.ESTOP.value,
lines=test_estop.build_csv_lines(),
),
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,20 @@ def get_hopper_door_closed(self) -> bool:
assert match, f"Incorrect Response for hopper door switch: {res}"
return bool(int(match.group(1)))

def get_estop(self) -> bool:
"""Get E-Stop status.
:return: True if E-Stop is triggered, False otherwise
"""
if self._simulating:
return True

_LS_RE = re.compile(r"^M112 (\d) OK\n")
res = self._send_and_recv("M112\n", "M112 ")
match = _LS_RE.match(res)
assert match, f"Incorrect Response for E-Stop switch: {res}"
return bool(int(match.group(1)))

def move_in_mm(
self, axis: StackerAxis, distance: float, params: MoveParams | None = None
) -> None:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Test Door Switch."""


from typing import List, Union
from hardware_testing.data import ui
from hardware_testing.data.csv_report import (
CSVReport,
CSVLine,
CSVLineRepeating,
CSVResult,
)

from .driver import FlexStacker


def build_csv_lines() -> List[Union[CSVLine, CSVLineRepeating]]:
"""Build CSV Lines."""
return [
CSVLine("close-door", [CSVResult]),
CSVLine("open-door", [CSVResult]),
]


def run(driver: FlexStacker, report: CSVReport, section: str) -> None:
"""Run."""
ui.print_header("Close Door")
if not driver._simulating:
ui.get_user_ready("Close the hopper door")
closed = driver.get_hopper_door_closed()
report(section, "close-door", [CSVResult.from_bool(closed)])

ui.print_header("Open Door")
if not driver._simulating:
ui.get_user_ready("Open the hopper door")
closed = driver.get_hopper_door_closed()
report(section, "open-door", [CSVResult.from_bool(not closed)])
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Test E-Stop."""


from typing import List, Union
from hardware_testing.data import ui
from hardware_testing.data.csv_report import (
CSVReport,
CSVLine,
CSVLineRepeating,
CSVResult,
)

from .driver import FlexStacker, Direction, StackerAxis


def build_csv_lines() -> List[Union[CSVLine, CSVLineRepeating]]:
"""Build CSV Lines."""
return [
CSVLine("trigger-estop", [CSVResult]),
CSVLine("x-move-disabled", [CSVResult]),
CSVLine("z-move-disabled", [CSVResult]),
CSVLine("l-move-disabled", [CSVResult]),
CSVLine("untrigger-estop", [CSVResult]),
]


def axis_at_limit(driver: FlexStacker, axis: StackerAxis) -> Direction:
"""Check which direction an axis is at the limit switch."""
if axis is StackerAxis.L:
# L axis only has one limit switch
if driver.get_limit_switch(axis, Direction.RETRACT):
print(axis, "is at ", Direction.RETRACT, "limit switch")
return Direction.RETRACT
else:
for dir in Direction:
if driver.get_limit_switch(axis, dir):
print(axis, "is at ", dir, "limit switch")
return dir
raise RuntimeError(f"{axis} is not at any limit switch")


def run(driver: FlexStacker, report: CSVReport, section: str) -> None:
"""Run."""
if not driver._simulating and driver.get_estop():
raise RuntimeError("E-Stop is either triggered/not attached.")

x_limit = axis_at_limit(driver, StackerAxis.X)
z_limit = axis_at_limit(driver, StackerAxis.Z)
l_limit = axis_at_limit(driver, StackerAxis.L)

ui.print_header("Trigger E-Stop")
if not driver._simulating:
ui.get_user_ready("Trigger the E-Stop")

if not driver.get_estop():
print("E-Stop is not triggered")
report(section, "trigger-estop", [CSVResult.FAIL])
return

report(section, "trigger-estop", [CSVResult.PASS])

print("try to move X axis...")
driver.move_in_mm(StackerAxis.X, x_limit.opposite().distance(10))
print("X should not move")
report(
section,
"x-move-disabled",
[CSVResult.from_bool(driver.get_limit_switch(StackerAxis.X, x_limit))],
)

print("try to move Z axis...")
driver.move_in_mm(StackerAxis.Z, z_limit.opposite().distance(10))
print("Z should not move")
report(
section,
"z-move-disabled",
[CSVResult.from_bool(driver.get_limit_switch(StackerAxis.Z, z_limit))],
)

print("try to move L axis...")
driver.move_in_mm(StackerAxis.L, l_limit.opposite().distance(10))
print("L should not move")
report(
section,
"l-move-disabled",
[CSVResult.from_bool(driver.get_limit_switch(StackerAxis.L, l_limit))],
)

if not driver._simulating:
ui.get_user_ready("Untrigger the E-Stop")
report(section, "untrigger-estop", [CSVResult.from_bool(not driver.get_estop())])
12 changes: 5 additions & 7 deletions hardware-testing/hardware_testing/scripts/ABRAsairScript.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import sys
import paramiko as pmk
import time
import json
import multiprocessing
from typing import Optional, List, Any

Expand Down Expand Up @@ -69,11 +70,10 @@ def run(file_name: str) -> List[Any]:
robot_ips = []
robot_names = []
with open(file_name) as file:
for line in file.readlines():
info = line.split(",")
if "Y" in info[2]:
robot_ips.append(info[0])
robot_names.append(info[1])
file_dict = json.load(file)
robot_dict = file_dict.get("ip_address_list")
robot_ips = list(robot_dict.keys())
robot_names = list(robot_dict.values())
print("Executing Script on All Robots:")
# Launch the processes for each robot.
processes = []
Expand All @@ -89,10 +89,8 @@ def run(file_name: str) -> List[Any]:
# Wait for all processes to finish.
file_name = sys.argv[1]
processes = run(file_name)

for process in processes:
process.start()
time.sleep(20)

for process in processes:
process.join()

0 comments on commit 3e0390e

Please sign in to comment.