Skip to content

Commit

Permalink
Merge branch 'edge' into robot-accuracy-recording
Browse files Browse the repository at this point in the history
  • Loading branch information
rclarke0 authored May 23, 2024
2 parents 92daf4e + 39dee3a commit e4bb0b4
Show file tree
Hide file tree
Showing 141 changed files with 2,399 additions and 650 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/hardware-testing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ on:
paths:
- 'Makefile'
- 'hardware-testing/**'
- 'api/**'
- 'shared-data/**'
- 'hardware/**'
- '.github/workflows/hardware-testing.yaml'
- '.github/actions/python/**'
branches:
Expand Down
3 changes: 2 additions & 1 deletion abr-testing/abr_testing/data_collection/abr_robot_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ def get_error_info_from_robot(
headers_lpc,
) = abr_google_drive.create_data_dictionary(
run_id, error_folder_path, issue_url, "", ""
)



start_row = google_sheet.get_index_row() + 1
google_sheet.batch_update_cells(runs_and_robots, "A", start_row, "0")
Expand Down
4 changes: 3 additions & 1 deletion abr-testing/abr_testing/data_collection/read_robot_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ def lpc_data(
for item in unique_offsets:
runs_and_lpc.append(unique_offsets[item].values())
headers_lpc = list(unique_offsets[(slot, labware_type)].keys())

return runs_and_lpc, headers_lpc


Expand Down Expand Up @@ -296,6 +295,7 @@ def create_abr_data_sheet(
def get_error_info(file_results: Dict[str, Any]) -> Tuple[int, str, str, str, str]:
"""Determines if errors exist in run log and documents them."""
error_levels = []
error_level = ""
# Read error levels file
with open(ERROR_LEVELS_PATH, "r") as error_file:
error_levels = list(csv.reader(error_file))
Expand Down Expand Up @@ -329,6 +329,8 @@ def get_error_info(file_results: Dict[str, Any]) -> Tuple[int, str, str, str, st
code_error = error[1]
if code_error == error_code:
error_level = error[4]
if len(error_level) < 1:
error_level = str(4)

return num_of_errors, error_type, error_code, error_instrument, error_level

Expand Down
27 changes: 27 additions & 0 deletions api-client/src/modules/__fixtures__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const mockModulesResponse = [
hasAvailableUpdate: false,
moduleType: 'thermocyclerModuleType',
moduleModel: 'thermocyclerModuleV1',
compatibleWithRobot: true,
data: {
status: 'holding at target',
currentTemperature: 3.0,
Expand All @@ -31,6 +32,7 @@ export const mockModulesResponse = [
hasAvailableUpdate: false,
moduleType: 'heaterShakerModuleType',
moduleModel: 'heaterShakerModuleV1',
compatibleWithRobot: true,
data: {
status: 'idle',
labwareLatchStatus: 'idle_unknown',
Expand All @@ -55,6 +57,7 @@ export const mockModulesResponse = [
hasAvailableUpdate: false,
moduleType: 'temperatureModuleType',
moduleModel: 'temperatureModuleV1',
compatibleWithRobot: true,
data: {
status: 'holding at target',
currentTemperature: 3.0,
Expand All @@ -75,6 +78,7 @@ export const mockModulesResponse = [
hasAvailableUpdate: false,
moduleType: 'magneticModuleType',
moduleModel: 'magneticModuleV1',
compatibleWithRobot: true,
data: {
status: 'engaged',
engaged: true,
Expand All @@ -89,6 +93,29 @@ export const mockModulesResponse = [
},
]

export const mockUnknownModuleResponse = [
...mockModulesResponse,
{
name: 'unknown',
displayName: 'UnknownModule',
moduleModel: 'unknownModule',
port: '/dev/unknown',
usbPort: {
port: 0,
hub: false,
portGroup: 'unknown',
path: '',
},
serial: 'dummySerialMD',
model: 'unknown_v1.1',
revision: 'unknown_v1.1',
fwVersion: 'dummyVersionMD',
hasAvailableUpdate: false,
status: 'engaged',
data: {},
},
]

export const v2MockModulesResponse = [
{
name: 'thermocycler',
Expand Down
1 change: 1 addition & 0 deletions api-client/src/modules/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface ApiBaseModule {
firmwareVersion: string
hasAvailableUpdate: boolean
usbPort: PhysicalPort
compatibleWithRobot?: boolean
moduleOffset?: ModuleOffset
}

Expand Down
22 changes: 12 additions & 10 deletions api-client/src/runs/commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ export interface CommandDetail {
}

export interface CommandsLinks {
current: {
// link to the currently executing command
href: string
meta: {
runId: string
commandId: string
key: string
createdAt: string
index: number
}
current?: CommandsLink | null
currentlyRecoveringFrom?: CommandsLink | null
}

interface CommandsLink {
href: string
meta: {
runId: string
commandId: string
key: string
createdAt: string
index: number
}
}

Expand Down
2 changes: 2 additions & 0 deletions api/.flake8
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ noqa-require-code = true
# string lints in these modules; remove entries as they are fixed
per-file-ignores =
setup.py:ANN,D
src/opentrons/__init__.py:ANN,D
src/opentrons/execute.py:ANN,D
src/opentrons/simulate.py:ANN,D
src/opentrons/types.py:ANN,D
Expand All @@ -46,6 +47,7 @@ per-file-ignores =
src/opentrons/util/linal.py:ANN,D
src/opentrons/util/entrypoint_util.py:ANN,D
src/opentrons/util/helpers.py:ANN,D
tests/opentrons/test_init.py:ANN,D
tests/opentrons/test_types.py:ANN,D
tests/opentrons/conftest.py:ANN,D
tests/opentrons/calibration_storage/*:ANN,D
Expand Down
132 changes: 113 additions & 19 deletions api/src/opentrons/__init__.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
# noqa: D104
import os

from pathlib import Path
import logging
from typing import List

from opentrons.config import feature_flags as ff
import re
from typing import Any, List, Tuple

from opentrons.drivers.serial_communication import get_ports_by_name
from opentrons.hardware_control import (
API as HardwareAPI,
ThreadManager,
ThreadManagedHardware,
types as hw_types,
)

from opentrons.config import (
feature_flags as ff,
name,
robot_configs,
IS_ROBOT,
ROBOT_FIRMWARE_DIR,
)
from opentrons.util import logging_config
from opentrons.protocols.types import ApiDeprecationError
from opentrons.protocols.api_support.types import APIVersion

from ._version import version

HERE = os.path.abspath(os.path.dirname(__file__))
__version__ = version


LEGACY_MODULES = ["robot", "reset", "instruments", "containers", "labware", "modules"]


__all__ = ["version", "__version__", "config"]
__all__ = ["version", "__version__", "HERE", "config"]


def __getattr__(attrname: str) -> None:
"""Prevent import of legacy modules from global.
This is to officially deprecate Python API Version 1.0.
"""
Prevent import of legacy modules from global to officially
deprecate Python API Version 1.0.
"""
if attrname in LEGACY_MODULES:
# Local imports for performance. This case is not hit frequently, and we
# don't want to drag these imports in any time anything is imported from
# anywhere in the `opentrons` package.
from opentrons.protocols.types import ApiDeprecationError
from opentrons.protocols.api_support.types import APIVersion

raise ApiDeprecationError(APIVersion(1, 0))
raise AttributeError(attrname)

Expand All @@ -39,18 +53,98 @@ def __dir__() -> List[str]:
log = logging.getLogger(__name__)


# todo(mm, 2024-05-15): Having functions in the package's top-level __init__.py
# can cause problems with import performance and circular dependencies. Can this
# be moved elsewhere?
SMOOTHIE_HEX_RE = re.compile("smoothie-(.*).hex")


def _find_smoothie_file() -> Tuple[Path, str]:
resources: List[Path] = []

# Search for smoothie files in /usr/lib/firmware first then fall back to
# value packed in wheel
if IS_ROBOT:
resources.extend(ROBOT_FIRMWARE_DIR.iterdir()) # type: ignore

resources_path = Path(HERE) / "resources"
resources.extend(resources_path.iterdir())

for path in resources:
matches = SMOOTHIE_HEX_RE.search(path.name)
if matches:
branch_plus_ref = matches.group(1)
return path, branch_plus_ref
raise OSError(f"Could not find smoothie firmware file in {resources_path}")


def _get_motor_control_serial_port() -> Any:
port = os.environ.get("OT_SMOOTHIE_EMULATOR_URI")

if port is None:
smoothie_id = os.environ.get("OT_SMOOTHIE_ID", "AMA")
# TODO(mc, 2021-08-01): raise a more informative exception than
# IndexError if a valid serial port is not found
port = get_ports_by_name(device_name=smoothie_id)[0]

log.info(f"Connecting to motor controller at port {port}")
return port


def should_use_ot3() -> bool:
"""Return true if ot3 hardware controller should be used."""
if ff.enable_ot3_hardware_controller():
try:
# Try this OT-3-specific import as an extra check in case the feature
# flag is mistakenly enabled on an OT-2 for some reason.
from opentrons_hardware.drivers.can_bus import CanDriver # noqa: F401

return True
except ModuleNotFoundError:
log.exception("Cannot use OT3 Hardware controller.")
return False


async def _create_thread_manager() -> ThreadManagedHardware:
"""Build the hardware controller wrapped in a ThreadManager.
.. deprecated:: 4.6
ThreadManager is on its way out.
"""
if os.environ.get("ENABLE_VIRTUAL_SMOOTHIE"):
log.info("Initialized robot using virtual Smoothie")
thread_manager: ThreadManagedHardware = ThreadManager(
HardwareAPI.build_hardware_simulator
)
elif should_use_ot3():
from opentrons.hardware_control.ot3api import OT3API

thread_manager = ThreadManager(
ThreadManager.nonblocking_builder(OT3API.build_hardware_controller),
use_usb_bus=ff.rear_panel_integration(),
status_bar_enabled=ff.status_bar_enabled(),
feature_flags=hw_types.HardwareFeatureFlags.build_from_ff(),
)
else:
thread_manager = ThreadManager(
ThreadManager.nonblocking_builder(HardwareAPI.build_hardware_controller),
port=_get_motor_control_serial_port(),
firmware=_find_smoothie_file(),
feature_flags=hw_types.HardwareFeatureFlags.build_from_ff(),
)

try:
await thread_manager.managed_thread_ready_async()
except RuntimeError:
log.exception("Could not build hardware controller, forcing virtual")
thread_manager = ThreadManager(HardwareAPI.build_hardware_simulator)

return thread_manager


async def initialize() -> ThreadManagedHardware:
"""
Initialize the Opentrons hardware returning a hardware instance.
"""
robot_conf = robot_configs.load()
logging_config.log_init(robot_conf.log_level)

log.info(f"API server version: {version}")
log.info(f"Robot Name: {name()}")

return await _create_thread_manager()
6 changes: 0 additions & 6 deletions api/src/opentrons/_resources_path.py

This file was deleted.

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 @@ -25,7 +25,6 @@
DEFAULT_LIQUID_PROBE_SETTINGS: Final[LiquidProbeSettings] = LiquidProbeSettings(
starting_mount_height=100,
max_z_distance=40,
min_z_distance=5,
mount_speed=10,
plunger_speed=5,
sensor_threshold_pascals=40,
Expand Down Expand Up @@ -337,7 +336,6 @@ def _build_default_liquid_probe(
"starting_mount_height", default.starting_mount_height
),
max_z_distance=from_conf.get("max_z_distance", default.max_z_distance),
min_z_distance=from_conf.get("min_z_distance", default.min_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
Loading

0 comments on commit e4bb0b4

Please sign in to comment.