Skip to content

Commit

Permalink
Merge branch 'edge' into feat_app-historicalprotocolrun
Browse files Browse the repository at this point in the history
  • Loading branch information
ncdiehl11 committed Jun 21, 2024
2 parents 57a1623 + 2429830 commit 89673b8
Show file tree
Hide file tree
Showing 916 changed files with 17,240 additions and 5,951 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ module.exports = {
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unnecessary-type-assertion': 'warn',
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
'@typescript-eslint/consistent-indexed-object-style': 'warn',
'@typescript-eslint/ban-types': 'warn',
'@typescript-eslint/non-nullable-type-assertion-style': 'warn',
Expand All @@ -126,11 +125,13 @@ module.exports = {
'**/__fixtures__/**.@(js|ts|tsx)',
'**/fixtures/**.@(js|ts|tsx)',
'scripts/*.@(js|ts|tsx)',
'**/**test.@(js|ts|tsx)',
],
rules: {
'@typescript-eslint/consistent-type-assertions': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-confusing-void-expression': 'warn',
'node/handle-callback-err': 'off',
},
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/components-test-build-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ jobs:
AWS_DEFAULT_REGION: us-east-2
run: |
aws s3 sync ./dist s3://opentrons-components/${{ env.OT_BRANCH}} --acl public-read
- name: 'Set generated URL'
run: |
echo 'Created component storybook for [${{ env.OT_BRANCH }}](https://s3-us-west-2.amazonaws.com/opentrons-components/${{ env.OT_BRANCH }}/index.html?path=/)' >> $GITHUB_STEP_SUMMARY
echo 'Created component storybook for https://s3-us-west-2.amazonaws.com/opentrons-components/${{ env.OT_BRANCH }}/index.html?path=/'
publish-components:
name: 'publish components package to npm'
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/js-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ jobs:
make setup-js
# Use the if to run all the lint checks even of some fail
shell: bash
- name: 'lint js'
if: always() && steps.setup-js.outcome == 'success'
run: make lint-js
- name: 'typechecks'
if: always() && steps.setup-js.outcome == 'success'
run: make check-js
- name: 'lint js'
if: always() && steps.setup-js.outcome == 'success'
run: make lint-js
- name: 'circular deps'
if: always() && steps.setup-js.outcome == 'success'
run: make circular-dependencies-js
Expand Down
30 changes: 30 additions & 0 deletions .storybook/preview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import { I18nextProvider } from 'react-i18next'
import { GlobalStyle } from '../app/src/atoms/GlobalStyle'
import { i18n } from '../app/src/i18n'

global.APP_SHELL_REMOTE = {
ipcRenderer: {
on: (topic, cb) => {},
invoke: (callname, args) => {},
send: (message, payload) => {}
},
}
global._PKG_VERSION_ = '0.0.0-storybook'

export const customViewports = {
onDeviceDisplay: {
name: 'Touchscreen',
Expand All @@ -12,6 +21,27 @@ export const customViewports = {
height: '600px',
},
},
desktopMinWidth: {
// retains a 4:3 aspect ratio... minHeight is not set so the user
// could drag the app up to a thin strip, but that's not terribly
// useful for viewing designs
name: 'Desktop Minimum Width',
type: 'desktop',
styles: {
width: '600px',
height: '450px'
}
},
desktopSmall: {
// A size typically used in figma app backgrounds, useful for viewing
// larger components in context
name: 'Desktop Typical Small',
type: 'desktop',
styles: {
width: '1024px',
height: '700px'
}
}
}

export const parameters = {
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ lint-js: lint-js-eslint lint-js-prettier

.PHONY: lint-js-eslint
lint-js-eslint:
yarn eslint --quiet=$(quiet) --ignore-pattern "node_modules/" --cache ".*.@(js|ts|tsx)" "**/*.@(js|ts|tsx)"
yarn eslint --quiet=$(quiet) --ignore-pattern "node_modules/" ".*.@(js|ts|tsx)" "**/*.@(js|ts|tsx)"

.PHONY: lint-js-prettier
lint-js-prettier:
Expand Down
7 changes: 7 additions & 0 deletions abr-testing/abr_testing/automation/jira_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ def get_jira_users(self, storage_directory: str) -> str:
print("JSON decoding error occurred.")
return file_path

def get_project_components(self, project_id: str) -> List[Dict[str, str]]:
"""Get list of components on JIRA board."""
component_url = f"{self.url}/rest/api/3/project/{project_id}/components"
response = requests.get(component_url, headers=self.headers, auth=self.auth)
components_list = response.json()
return components_list


if __name__ == "__main__":
"""Create ticket for specified robot."""
Expand Down
24 changes: 21 additions & 3 deletions abr-testing/abr_testing/data_collection/abr_robot_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@
import subprocess
import sys
import json
import re


def match_error_to_component(
project_id: str, error_message: str, components: List[str]
) -> List[str]:
"""Match error to component based on error message."""
project_components = ticket.get_project_components(project_id)
component_names = [proj_comp["name"] for proj_comp in project_components]
for component in component_names:
pattern = re.compile(component, re.IGNORECASE)
matches = pattern.findall(error_message)
if matches:
components.append(component)
return components


def get_user_id(user_file_path: str, assignee_name: str) -> str:
Expand Down Expand Up @@ -76,9 +91,10 @@ def get_robot_state(
)
module_data = response.json()
for module in module_data["data"]:
print(module)
description[module["moduleType"]] = module
components = ["Flex-RABR"]
components = match_error_to_component("RABR", reported_string, components)
print(components)
whole_description_str = (
"{"
+ "\n".join("{!r}: {!r},".format(k, v) for k, v in description.items())
Expand Down Expand Up @@ -116,6 +132,8 @@ def get_run_error_info_from_robot(
failure_level = "Level " + str(error_level) + " Failure"

components = [failure_level, "Flex-RABR"]
components = match_error_to_component("RABR", error_type, components)
print(components)
affects_version = results["API_Version"]
parent = results.get("robot_name", "")
print(parent)
Expand Down Expand Up @@ -211,8 +229,8 @@ def get_run_error_info_from_robot(
except requests.exceptions.InvalidURL:
print("Invalid IP address.")
sys.exit()
one_run = error_runs[-1] # Most recent run with error.
if len(run_or_other) < 1:
one_run = error_runs[-1] # Most recent run with error.
(
summary,
robot,
Expand All @@ -234,7 +252,7 @@ def get_run_error_info_from_robot(
ip, storage_directory
)
file_paths = read_robot_logs.get_logs(storage_directory, ip)
print(f"Making ticket for run: {one_run} on robot {robot}.")
print(f"Making ticket for {summary}.")
# TODO: make argument or see if I can get rid of with using board_id.
project_key = "RABR"
parent_key = project_key + "-" + robot[-1]
Expand Down
3 changes: 3 additions & 0 deletions abr-testing/abr_testing/data_collection/read_robot_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,9 @@ def get_error_info(file_results: Dict[str, Any]) -> Tuple[int, str, str, str, st
error_str = 0
if error_str > 1:
error_type = run_command_error["error"].get("errorType", "")
if error_type == "PythonException":
# Reassign error_type to be more descriptive
error_type = run_command_error["detail"].split(":")[0]
error_code = run_command_error["error"].get("errorCode", "")
try:
# Instrument Error
Expand Down
2 changes: 1 addition & 1 deletion api/docs/v2/new_examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ Opentrons electronic pipettes can do some things that a human cannot do with a p
p300.return_tip()
Notice here how Python's :py:class:`slice` functionality (in the code sample as ``[:4]``) lets us select the first five wells of the well plate only. Also, in Python, a range of numbers is *exclusive* of the end value and counting starts at 0, not 1. For the Corning 96-well plate used here, this means well A1=0, B1=1, C1=2, and so on to the last well used, which is E1=4. See also, the :ref:`tutorial-commands` section of the Tutorial.
Notice here how Python's :py:class:`slice` functionality (in the code sample as ``[:5]``) lets us select the first five wells of the well plate only. Also, in Python, a range of numbers is *exclusive* of the end value and counting starts at 0, not 1. For the USA Scientific 12-well reservoir used here, this means well A1=0, A2=1, A3=2, and so on to the last well used, which is A5=4. See also, the :ref:`tutorial-commands` section of the Tutorial.

Dilution
========
Expand Down
37 changes: 37 additions & 0 deletions api/src/opentrons/cli/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime, timezone
from enum import Enum
from pathlib import Path
from pydantic import BaseModel
from typing import (
Expand Down Expand Up @@ -48,6 +49,7 @@
Liquid,
StateSummary,
)
from opentrons.protocol_engine.protocol_engine import code_in_error_tree

from opentrons_shared_data.robot.dev_types import RobotType

Expand Down Expand Up @@ -302,6 +304,19 @@ async def _analyze(
if not outputs:
return return_code

if len(analysis.state_summary.errors) > 0:
if any(
code_in_error_tree(
root_error=error, code=ErrorCodes.RUNTIME_PARAMETER_VALUE_REQUIRED
)
for error in analysis.state_summary.errors
):
result = AnalysisResult.PARAMETER_VALUE_REQUIRED
else:
result = AnalysisResult.NOT_OK
else:
result = AnalysisResult.OK

results = AnalyzeResults.construct(
createdAt=datetime.now(tz=timezone.utc),
files=[
Expand All @@ -313,6 +328,7 @@ async def _analyze(
if isinstance(protocol_source.config, JsonProtocolConfig)
else PythonConfig.construct(apiVersion=protocol_source.config.api_version)
),
result=result,
metadata=protocol_source.metadata,
robotType=protocol_source.robot_type,
runTimeParameters=analysis.parameters,
Expand Down Expand Up @@ -365,6 +381,26 @@ class PythonConfig(BaseModel):
apiVersion: APIVersion


class AnalysisResult(str, Enum):
"""Result of a completed protocol analysis.
The result indicates whether the protocol is expected to run successfully.
Properties:
OK: No problems were found during protocol analysis.
NOT_OK: Problems were found during protocol analysis. Inspect
`analysis.errors` for error occurrences.
PARAMETER_VALUE_REQUIRED: A value is required to be set for a parameter
in order for the protocol to be analyzed/run. The absence of this does not
inherently mean there are no parameters, as there may be defaults for all
or unset parameters are not referenced or handled via try/except clauses.
"""

OK = "ok"
NOT_OK = "not-ok"
PARAMETER_VALUE_REQUIRED = "parameter-value-required"


class AnalyzeResults(BaseModel):
"""Results of a protocol analysis.
Expand All @@ -381,6 +417,7 @@ class AnalyzeResults(BaseModel):
metadata: Dict[str, Any]

# Fields that should match robot-server:
result: AnalysisResult
robotType: RobotType
runTimeParameters: List[RunTimeParameter]
commands: List[Command]
Expand Down
2 changes: 0 additions & 2 deletions api/src/opentrons/config/defaults_ot3.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

DEFAULT_LIQUID_PROBE_SETTINGS: Final[LiquidProbeSettings] = LiquidProbeSettings(
starting_mount_height=100,
max_z_distance=40,
mount_speed=10,
plunger_speed=5,
sensor_threshold_pascals=40,
Expand Down Expand Up @@ -335,7 +334,6 @@ def _build_default_liquid_probe(
starting_mount_height=from_conf.get(
"starting_mount_height", default.starting_mount_height
),
max_z_distance=from_conf.get("max_z_distance", default.max_z_distance),
mount_speed=from_conf.get("mount_speed", default.mount_speed),
plunger_speed=from_conf.get("plunger_speed", default.plunger_speed),
sensor_threshold_pascals=from_conf.get(
Expand Down
1 change: 0 additions & 1 deletion api/src/opentrons/config/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ class ZSenseSettings:
@dataclass
class LiquidProbeSettings:
starting_mount_height: float
max_z_distance: float
mount_speed: float
plunger_speed: float
sensor_threshold_pascals: float
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/hardware_control/backends/ot3controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@
PipetteOverpressureError,
FirmwareUpdateRequiredError,
FailedGripperPickupError,
LiquidNotFoundError,
PipetteLiquidNotFoundError,
CommunicationError,
PythonException,
UnsupportedHardwareCommand,
Expand Down Expand Up @@ -1412,7 +1412,7 @@ async def liquid_probe(
or positions[head_node].move_ack
== MoveCompleteAck.complete_without_condition
):
raise LiquidNotFoundError(
raise PipetteLiquidNotFoundError(
"Liquid not found during probe.",
{
str(node_to_axis(node)): str(point.motor_position)
Expand Down
23 changes: 12 additions & 11 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
GripperNotPresentError,
InvalidActuator,
FirmwareUpdateFailedError,
LiquidNotFoundError,
PipetteLiquidNotFoundError,
)

from .util import use_or_initialize_loop, check_motion_bounds
Expand Down Expand Up @@ -2594,7 +2594,8 @@ def _get_probe_distances(

async def liquid_probe(
self,
mount: OT3Mount,
mount: Union[top_types.Mount, OT3Mount],
max_z_dist: float,
probe_settings: Optional[LiquidProbeSettings] = None,
probe: Optional[InstrumentProbeType] = None,
) -> float:
Expand All @@ -2605,7 +2606,7 @@ async def liquid_probe(
reading from the pressure sensor.
If the move is completed without the specified threshold being triggered, a
LiquidNotFoundError error will be thrown.
PipetteLiquidNotFoundError error will be thrown.
Otherwise, the function will stop moving once the threshold is triggered,
and return the position of the
Expand All @@ -2622,21 +2623,21 @@ async def liquid_probe(
if not probe_settings:
probe_settings = self.config.liquid_sense

pos = await self.gantry_position(mount, refresh=True)
pos = await self.gantry_position(checked_mount, refresh=True)
probe_start_pos = pos._replace(z=probe_settings.starting_mount_height)
await self.move_to(mount, probe_start_pos)
total_z_travel = probe_settings.max_z_distance
await self.move_to(checked_mount, probe_start_pos)
total_z_travel = max_z_dist
z_travels = self._get_probe_distances(
checked_mount,
total_z_travel,
probe_settings.plunger_speed,
probe_settings.mount_speed,
)
error: Optional[LiquidNotFoundError] = None
error: Optional[PipetteLiquidNotFoundError] = None
for z_travel in z_travels:

if probe_settings.aspirate_while_sensing:
await self._move_to_plunger_bottom(mount, rate=1.0)
await self._move_to_plunger_bottom(checked_mount, rate=1.0)
else:
# find the ideal travel distance by multiplying the plunger speed
# by the time it will take to complete the z move.
Expand All @@ -2656,17 +2657,17 @@ async def liquid_probe(
await self._move(target_pos, speed=speed, acquire_lock=True)
try:
height = await self._liquid_probe_pass(
mount,
checked_mount,
probe_settings,
probe if probe else InstrumentProbeType.PRIMARY,
z_travel,
)
# if we made it here without an error we found the liquid
error = None
break
except LiquidNotFoundError as lnfe:
except PipetteLiquidNotFoundError as lnfe:
error = lnfe
await self.move_to(mount, probe_start_pos)
await self.move_to(checked_mount, probe_start_pos)
if error is not None:
# if we never found an liquid raise an error
raise error
Expand Down
Loading

0 comments on commit 89673b8

Please sign in to comment.