Skip to content

Commit

Permalink
Merge branch 'chore_release-7.0.0' into app_ODD-analytics-setting-screen
Browse files Browse the repository at this point in the history
  • Loading branch information
brenthagen committed Sep 12, 2023
2 parents f3a8fbe + 6c10811 commit 77fc0b9
Show file tree
Hide file tree
Showing 33 changed files with 289 additions and 127 deletions.
3 changes: 2 additions & 1 deletion api/src/opentrons/calibration_storage/ot2/pipette_offset.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,13 @@ def get_pipette_offset(
**io.read_cal_file(pipette_calibration_filepath)
)
except FileNotFoundError:
log.warning(f"Calibrations for {pipette_id} on {mount} does not exist.")
log.debug(f"Calibrations for {pipette_id} on {mount} does not exist.")
return None
except (json.JSONDecodeError, ValidationError):
log.warning(
f"Malformed calibrations for {pipette_id} on {mount}. Please factory reset your calibrations."
)
# TODO: Delete the bad calibration here maybe?
return None


Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/calibration_storage/ot2/tip_length.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def tip_lengths_for_pipette(
pass
return tip_lengths
except FileNotFoundError:
log.warning(f"Tip length calibrations not found for {pipette_id}")
log.debug(f"Tip length calibrations not found for {pipette_id}")
return tip_lengths


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def get_pipette_offset(
**io.read_cal_file(pipette_calibration_filepath)
)
except FileNotFoundError:
log.warning(f"Calibrations for {pipette_id} on {mount} does not exist.")
log.debug(f"Calibrations for {pipette_id} on {mount} does not exist.")
return None
except (json.JSONDecodeError, ValidationError):
log.warning(
Expand Down
7 changes: 5 additions & 2 deletions api/src/opentrons/calibration_storage/ot3/tip_length.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,22 @@ def tip_lengths_for_pipette(
) -> typing.Dict[str, v1.TipLengthModel]:
tip_lengths = {}
try:
# While you technically could drop some data in for tip length calibration on the flex,
# it is not necessary and there is no UI frontend for it, so this code will mostly be
# taking the FileNotFoundError path.
tip_length_filepath = config.get_tip_length_cal_path() / f"{pipette_id}.json"
all_tip_lengths_for_pipette = io.read_cal_file(tip_length_filepath)
for tiprack, data in all_tip_lengths_for_pipette.items():
try:
tip_lengths[tiprack] = v1.TipLengthModel(**data)
except (json.JSONDecodeError, ValidationError):
log.warning(
log.debug(
f"Tip length calibration is malformed for {tiprack} on {pipette_id}"
)
pass
return tip_lengths
except FileNotFoundError:
log.warning(f"Tip length calibrations not found for {pipette_id}")
# this is the overwhelmingly common case
return tip_lengths


Expand Down
6 changes: 5 additions & 1 deletion api/src/opentrons/hardware_control/backends/ot3simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ def _sanitize_attached_instrument(
nodes.add(NodeId.gripper)
self._present_nodes = nodes
self._current_settings: Optional[OT3AxisMap[CurrentConfig]] = None
self._sim_jaw_state = GripperJawState.HOMED_READY

@property
def initialized(self) -> bool:
Expand Down Expand Up @@ -369,12 +370,14 @@ async def gripper_grip_jaw(
) -> None:
"""Move gripper inward."""
_ = create_gripper_jaw_grip_group(duty_cycle, stop_condition, stay_engaged)
self._sim_jaw_state = GripperJawState.GRIPPING

@ensure_yield
async def gripper_home_jaw(self, duty_cycle: float) -> None:
"""Move gripper outward."""
_ = create_gripper_jaw_home_group(duty_cycle)
self._motor_status[NodeId.gripper_g] = MotorStatus(True, True)
self._sim_jaw_state = GripperJawState.HOMED_READY

@ensure_yield
async def gripper_hold_jaw(
Expand All @@ -383,6 +386,7 @@ async def gripper_hold_jaw(
) -> None:
_ = create_gripper_jaw_hold_group(encoder_position_um)
self._encoder_position[NodeId.gripper_g] = encoder_position_um / 1000.0
self._sim_jaw_state = GripperJawState.HOLDING

async def get_tip_present(self, mount: OT3Mount, tip_state: TipStateType) -> None:
"""Raise an error if the given state doesn't match the physical state."""
Expand All @@ -394,7 +398,7 @@ async def get_tip_present_state(self, mount: OT3Mount) -> int:

async def get_jaw_state(self) -> GripperJawState:
"""Get the state of the gripper jaw."""
return GripperJawState.HOMED_READY
return self._sim_jaw_state

async def tip_action(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
from opentrons.types import Point, Mount

# TODO change this when imports are fixed
from opentrons.calibration_storage.ot3.pipette_offset import save_pipette_calibration
from opentrons.calibration_storage.ot3.pipette_offset import (
save_pipette_calibration,
get_pipette_offset,
)
from opentrons.calibration_storage import (
types as cal_top_types,
get_pipette_offset,
ot3_gripper_offset,
)
from opentrons.hardware_control.types import OT3Mount
Expand Down
46 changes: 42 additions & 4 deletions api/src/opentrons/protocol_engine/errors/error_occurrence.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,55 @@ def from_failed(
)

id: str = Field(..., description="Unique identifier of this error occurrence.")
errorType: str = Field(..., description="Specific error type that occurred.")
createdAt: datetime = Field(..., description="When the error occurred.")
detail: str = Field(..., description="A human-readable message about the error.")

errorCode: str = Field(
default=ErrorCodes.GENERAL_ERROR.value.code,
description="An enumerated error code for the error type.",
description=(
"An enumerated error code for the error type."
" This is intended to be shown to the robot operator to direct them to the"
" correct rough area for troubleshooting."
),
)

# TODO(mm, 2023-09-07):
# The Opentrons App and Flex ODD use `errorType` in the title of error modals, but it's unclear
# if they should. Is this field redundant now that we have `errorCode` and `detail`?
#
# In practice, this is often the source code name of our Python exception type.
# Should we derive this from `errorCode` instead? Like how HTTP code 404 is always "not found."
errorType: str = Field(
...,
description=(
"A short name for the error type that occurred, like `PipetteOverpressure`."
" This can be a bit more specific than `errorCode`."
),
)

detail: str = Field(
...,
description=(
"A short human-readable message about the error."
"\n\n"
"This is intended to provide the robot operator with more specific details than"
" `errorCode` alone. It should be no longer than a couple of sentences,"
" and it should not contain internal newlines or indentation."
"\n\n"
" It should not internally repeat `errorCode`, but it may internally repeat `errorType`"
" if it helps the message make sense when it's displayed in its own separate block."
),
)

errorInfo: Dict[str, str] = Field(
default={},
description="Specific details about the error that may be useful for determining cause.",
description=(
"Specific details about the error that may be useful for determining cause."
" This might contain the same information as `detail`, but in a more structured form."
" It might also contain additional information that was too verbose or technical"
" to put in `detail`."
),
)

wrappedErrors: List["ErrorOccurrence"] = Field(
default=[], description="Errors that may have caused this one."
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ async def move_labware_with_gripper(

for waypoint_data in movement_waypoints:
if waypoint_data.jaw_open:
await ot3api.home_gripper_jaw()
await ot3api.ungrip()
else:
await ot3api.grip(force_newtons=labware_grip_force)
await ot3api.move_to(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,5 @@ def _get_calibrated_tip_length_sync(
f"No calibrated tip length found for {pipette_serial},"
f" using nominal fallback value of {nominal_fallback}"
)
log.warning(message, exc_info=e)
log.debug(message, exc_info=e)
return nominal_fallback
16 changes: 0 additions & 16 deletions api/tests/opentrons/hardware_control/test_ot3_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,19 +128,6 @@ def mock_move_to(ot3_hardware: ThreadManager[OT3API]) -> Iterator[AsyncMock]:
yield mock_move


@pytest.fixture
def mock_get_jaw_state(ot3_hardware: ThreadManager[OT3API]) -> Iterator[AsyncMock]:
with patch.object(
ot3_hardware.managed_obj._backend,
"get_jaw_state",
AsyncMock(
spec=ot3_hardware.managed_obj._backend.get_jaw_state,
wraps=ot3_hardware.managed_obj._backend.get_jaw_state,
),
) as mock_get_jaw_state:
yield mock_get_jaw_state


@pytest.fixture
def mock_home(ot3_hardware: ThreadManager[OT3API]) -> Iterator[AsyncMock]:
with patch.object(
Expand Down Expand Up @@ -852,14 +839,12 @@ async def test_gripper_capacitive_sweep(
distance: float,
ot3_hardware: ThreadManager[OT3API],
mock_move_to: AsyncMock,
mock_get_jaw_state: AsyncMock,
mock_backend_capacitive_pass: AsyncMock,
gripper_present: None,
) -> None:
await ot3_hardware.home()
await ot3_hardware.grip(5)
ot3_hardware._gripper_handler.get_gripper().current_jaw_displacement = 5
mock_get_jaw_state.return_value = GripperJawState.GRIPPING
ot3_hardware.add_gripper_probe(probe)
data = await ot3_hardware.capacitive_sweep(OT3Mount.GRIPPER, axis, begin, end, 3)
assert data == [1, 2, 3, 4, 5, 6, 8]
Expand Down Expand Up @@ -1783,7 +1768,6 @@ async def test_estop_event_deactivate_module(
async def test_stop_only_home_necessary_axes(
ot3_hardware: ThreadManager[OT3API],
mock_home: AsyncMock,
# mock_get_jaw_state: AsyncMock,
mock_reset: AsyncMock,
jaw_state: GripperJawState,
):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ async def test_move_labware_with_gripper(

decoy.when(state_store.config.use_virtual_gripper).then_return(False)
decoy.when(ot3_hardware_api.has_gripper()).then_return(True)
decoy.when(ot3_hardware_api._gripper_handler.is_ready_for_jaw_home()).then_return(
True
)

decoy.when(
await ot3_hardware_api.gantry_position(mount=OT3Mount.GRIPPER)
Expand Down
9 changes: 7 additions & 2 deletions app-shell/src/protocol-storage/file-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ import { analyzeProtocolSource } from '../protocol-analysis'
* │ ├─ analysis/
* │ │ ├─ 1646303906.json
*/

export const PROTOCOLS_DIRECTORY_NAME = 'protocols'
// TODO(jh, 2023-09-11): remove OLD_PROTOCOLS_DIRECTORY_PATH after
// OT-2 parity work is completed and move all protocols back to "protocols" directory.
export const OLD_PROTOCOLS_DIRECTORY_PATH = path.join(
app.getPath('userData'),
'protocols'
)
export const PROTOCOLS_DIRECTORY_NAME = 'protocols_v7.0-supported'
export const PROTOCOLS_DIRECTORY_PATH = path.join(
app.getPath('userData'),
PROTOCOLS_DIRECTORY_NAME
Expand Down
54 changes: 54 additions & 0 deletions app-shell/src/protocol-storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,65 @@ export const getProtocolSrcFilePaths = (
})
}

// TODO(jh, 2023-09-11): remove migrateProtocolsToNewDirectory after
// OT-2 parity work is completed.
const migrateProtocols = migrateProtocolsToNewDirectory()
function migrateProtocolsToNewDirectory(): () => Promise<void> {
let hasCheckedForMigration = false
return function (): Promise<void> {
return new Promise((resolve, reject) => {
if (hasCheckedForMigration) resolve()
hasCheckedForMigration = true
console.log(
`Performing protocol migration to ${FileSystem.PROTOCOLS_DIRECTORY_NAME}...`
)
copyProtocols(
FileSystem.OLD_PROTOCOLS_DIRECTORY_PATH,
FileSystem.PROTOCOLS_DIRECTORY_PATH
)
.then(() => {
console.log('Protocol migration complete.')
resolve()
})
.catch(e => {
console.log(
`Error migrating protocols to ${FileSystem.PROTOCOLS_DIRECTORY_NAME}: ${e}`
)
resolve()
})
})
}

function copyProtocols(src: string, dest: string): Promise<void> {
return fse
.stat(src)
.then(doesSrcExist => {
if (!doesSrcExist.isDirectory()) return Promise.resolve()

return fse.readdir(src).then(items => {
const protocols = items.map(item => {
const srcItem = path.join(src, item)
const destItem = path.join(dest, item)

return fse.copy(srcItem, destItem, {
overwrite: false,
})
})
return Promise.all(protocols).then(() => Promise.resolve())
})
})
.catch(e => {
return Promise.reject(e)
})
}
}

export const fetchProtocols = (
dispatch: Dispatch,
source: ListSource
): Promise<void> => {
return ensureDir(FileSystem.PROTOCOLS_DIRECTORY_PATH)
.then(() => migrateProtocols())
.then(() =>
FileSystem.readDirectoriesWithinDirectory(
FileSystem.PROTOCOLS_DIRECTORY_PATH
Expand Down
6 changes: 2 additions & 4 deletions app/src/assets/localization/en/device_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@
"device_reset_slideout_description": "Select individual settings to only clear specific data types.",
"device_resets_cannot_be_undone": "Resets cannot be undone",
"directly_connected_to_this_computer": "Directly connected to this computer.",
"disable_homing": "Disable homing the gantry when restarting robot",
"disable_homing_description": "Prevent robot from homing motors when the robot restarts.",
"disconnect": "Disconnect",
"disconnect_from_ssid": "Disconnect from {{ssid}}",
"disconnect_from_wifi": "Disconnect from Wi-Fi",
Expand Down Expand Up @@ -128,15 +126,15 @@
"finish_setup": "Finish setup",
"firmware_version": "Firmware Version",
"fully_calibrate_before_checking_health": "Fully calibrate your robot before checking calibration health",
"gantry_homing": "Home Gantry on Restart",
"gantry_homing_description": "Homes the gantry along the z-axis.",
"go_to_advanced_settings": "Go to Advanced App Settings",
"gripper_calibration_description": "Gripper calibration uses a metal pin to determine the gripper's exact position relative to precision-cut squares on deck slots.",
"gripper_calibration_title": "Gripper Calibration",
"gripper_serial": "Gripper Serial",
"health_check": "Check health",
"hide": "Hide",
"historic_offsets_description": "Use stored data when setting up a protocol.",
"home_gantry_on_restart": "Home gantry on restart",
"home_gantry_subtext": "By default, this setting is turned on.",
"incorrect_password_for_ssid": "Oops! Incorrect password for {{ssid}}",
"install_e_stop": "Install the E-stop",
"installing_software": "Installing software...",
Expand Down
6 changes: 0 additions & 6 deletions app/src/organisms/Devices/ProtocolRun/RunFailedModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react'
import isEmpty from 'lodash/isEmpty'
import { useTranslation } from 'react-i18next'
import {
ALIGN_CENTER,
Expand Down Expand Up @@ -95,11 +94,6 @@ export function RunFailedModal({
<StyledText as="p" textAlign={TYPOGRAPHY.textAlignLeft}>
{highestPriorityError.detail}
</StyledText>
{!isEmpty(highestPriorityError.errorInfo) && (
<StyledText as="p" textAlign={TYPOGRAPHY.textAlignLeft}>
{JSON.stringify(highestPriorityError.errorInfo)}
</StyledText>
)}
</Flex>
<StyledText as="p">
{t('run_failed_modal_description_desktop')}
Expand Down
Loading

0 comments on commit 77fc0b9

Please sign in to comment.