Skip to content

Commit

Permalink
chore(merge): release v7.2.1 into edge
Browse files Browse the repository at this point in the history
  • Loading branch information
y3rsh committed Mar 19, 2024
2 parents 9f6b349 + 583dcf6 commit 5736292
Show file tree
Hide file tree
Showing 19 changed files with 218 additions and 63 deletions.
13 changes: 13 additions & 0 deletions api/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ log][]. For a list of currently known issues, please see the [Opentrons issue tr

---

## Opentrons Robot Software Changes in 7.2.1

Welcome to the v7.2.1 release of the Opentrons robot software!

### Bug Fixes

- Fixed an issue where OT-2 tip length calibrations created before v4.1.0 would cause a "missing calibration data" error that you could only resolve by resetting calibration.
- Fixed collision prediction being too conservative in certain conditions on Flex, leading to errors even when collisions wouldn't take place.
- Flex now properly homes after an instrument collision.
- `opentrons_simulate` now outputs entries for commands that drop tips in the default trash container in protocols that specify Python API version 2.16 or newer.

---

## Opentrons Robot Software Changes in 7.2.0

Welcome to the v7.2.0 release of the Opentrons robot software!
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/calibration_storage/ot2/deck_attitude.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def get_robot_deck_attitude() -> Optional[v1.DeckCalibrationModel]:
pass
except (json.JSONDecodeError, ValidationError):
log.warning(
"Deck calibration is malformed. Please factory reset your calibrations."
"Deck calibration is malformed. Please factory reset your calibrations.",
exc_info=True,
)
pass
return None
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 @@ -92,7 +92,8 @@ def get_pipette_offset(
return None
except (json.JSONDecodeError, ValidationError):
log.warning(
f"Malformed calibrations for {pipette_id} on {mount}. Please factory reset your calibrations."
f"Malformed calibrations for {pipette_id} on {mount}. Please factory reset your calibrations.",
exc_info=True,
)
# TODO: Delete the bad calibration here maybe?
return None
Expand Down
49 changes: 32 additions & 17 deletions api/src/opentrons/calibration_storage/ot2/tip_length.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,43 @@ def _convert_tip_length_model_to_dict(
def tip_lengths_for_pipette(
pipette_id: str,
) -> typing.Dict[LabwareUri, v1.TipLengthModel]:
tip_lengths = {}
try:
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_identifier, data in all_tip_lengths_for_pipette.items():
# We normally key these calibrations by their tip rack URI,
# but older software had them keyed by their tip rack hash.
# Migrate from the old format, if necessary.
if "/" not in tiprack_identifier:
data["definitionHash"] = tiprack_identifier
tiprack_identifier = data.pop("uri")
try:
tip_lengths[LabwareUri(tiprack_identifier)] = v1.TipLengthModel(**data)
except (json.JSONDecodeError, ValidationError):
log.warning(
f"Tip length calibration is malformed for {tiprack_identifier} on {pipette_id}"
)
pass
return tip_lengths
except FileNotFoundError:
log.debug(f"Tip length calibrations not found for {pipette_id}")
return tip_lengths
return {}
except json.JSONDecodeError:
log.warning(
f"Tip length calibration is malformed for {pipette_id}", exc_info=True
)
return {}

tip_lengths: typing.Dict[LabwareUri, v1.TipLengthModel] = {}

for tiprack_identifier, data in all_tip_lengths_for_pipette.items():
# We normally key these calibrations by their tip rack URI,
# but older software had them keyed by their tip rack hash.
# Migrate from the old format, if necessary.
tiprack_identifier_is_uri = "/" in tiprack_identifier
if not tiprack_identifier_is_uri:
data["definitionHash"] = tiprack_identifier
uri = data.pop("uri", None)
if uri is None:
# We don't have a way to migrate old records without a URI,
# so skip over them.
continue
else:
tiprack_identifier = uri

try:
tip_lengths[LabwareUri(tiprack_identifier)] = v1.TipLengthModel(**data)
except ValidationError:
log.warning(
f"Tip length calibration is malformed for {tiprack_identifier} on {pipette_id}",
exc_info=True,
)
return tip_lengths


def load_tip_length_calibration(
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/calibration_storage/ot3/deck_attitude.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def get_robot_belt_attitude() -> Optional[v1.BeltCalibrationModel]:
pass
except (json.JSONDecodeError, ValidationError):
log.warning(
"Belt calibration is malformed. Please factory reset your calibrations."
"Belt calibration is malformed. Please factory reset your calibrations.",
exc_info=True,
)
pass
return None
6 changes: 4 additions & 2 deletions api/src/opentrons/calibration_storage/ot3/module_offset.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ def get_module_offset(
return None
except (json.JSONDecodeError, ValidationError):
log.warning(
f"Malformed calibrations for {module_id} on slot {slot}. Please factory reset your calibrations."
f"Malformed calibrations for {module_id} on slot {slot}. Please factory reset your calibrations.",
exc_info=True,
)
return None

Expand All @@ -130,7 +131,8 @@ def load_all_module_offsets() -> List[v1.ModuleOffsetModel]:
)
except (json.JSONDecodeError, ValidationError):
log.warning(
f"Malformed module calibrations for {file}. Please factory reset your calibrations."
f"Malformed module calibrations for {file}. Please factory reset your calibrations.",
exc_info=True,
)
continue
return calibrations
3 changes: 2 additions & 1 deletion api/src/opentrons/calibration_storage/ot3/pipette_offset.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def get_pipette_offset(
return None
except (json.JSONDecodeError, ValidationError):
log.warning(
f"Malformed calibrations for {pipette_id} on {mount}. Please factory reset your calibrations."
f"Malformed calibrations for {pipette_id} on {mount}. Please factory reset your calibrations.",
exc_info=True,
)
return None
2 changes: 1 addition & 1 deletion api/src/opentrons/config/robot_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def _load_json(filename: Union[str, Path]) -> Dict[str, Any]:
log.warning("{0} not found. Loading defaults".format(filename))
res = {}
except json.decoder.JSONDecodeError:
log.warning("{0} is corrupt. Loading defaults".format(filename))
log.warning("{0} is corrupt. Loading defaults".format(filename), exc_info=True)
res = {}
return cast(Dict[str, Any], res)

Expand Down
5 changes: 1 addition & 4 deletions api/src/opentrons/protocol_api/core/engine/deck_conflict.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,6 @@ def __init__(self, message: str) -> None:
x=A12_column_back_right_bound.x - _NOZZLE_PITCH * 11, y=506.2
)

# Arbitrary safety margin in z-direction
Z_SAFETY_MARGIN = 10

_FLEX_TC_LID_BACK_LEFT_PT = Point(
x=FLEX_TC_LID_COLLISION_ZONE["back_left"]["x"],
y=FLEX_TC_LID_COLLISION_ZONE["back_left"]["y"],
Expand Down Expand Up @@ -332,7 +329,7 @@ def _slot_has_potential_colliding_object(
slot_highest_z = engine_state.geometry.get_highest_z_in_slot(
StagingSlotLocation(slotName=surrounding_slot)
)
return slot_highest_z + Z_SAFETY_MARGIN > pipette_bounds[0].z
return slot_highest_z >= pipette_bounds[0].z
return False


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 @@ -1149,6 +1149,7 @@ def home_plunger(self) -> InstrumentContext:
self._core.home_plunger()
return self

# TODO (spp, 2024-03-08): verify if ok to & change source & dest types to AdvancedLiquidHandling
@publisher.publish(command=cmds.distribute)
@requires_version(2, 0)
def distribute(
Expand Down Expand Up @@ -1188,6 +1189,7 @@ def distribute(

return self.transfer(volume, source, dest, **kwargs)

# TODO (spp, 2024-03-08): verify if ok to & change source & dest types to AdvancedLiquidHandling
@publisher.publish(command=cmds.consolidate)
@requires_version(2, 0)
def consolidate(
Expand Down
28 changes: 28 additions & 0 deletions api/tests/opentrons/calibration_storage/test_tip_length_ot2.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,31 @@ def test_delete_all_tip_calibration(starting_calibration_data: Any) -> None:
clear_tip_length_calibration()
assert tip_lengths_for_pipette("pip1") == {}
assert tip_lengths_for_pipette("pip2") == {}


def test_uriless_calibrations_are_dropped(ot_config_tempdir: object) -> None:
"""Legacy records without a `uri` field should be silently ignored."""

data = {
"ed323db6ca1ddf197aeb20667c1a7a91c89cfb2f931f45079d483928da056812": {
"tipLength": 123,
"lastModified": "2021-01-11T00:34:29.291073+00:00",
"source": "user",
"status": {"markedBad": False},
},
"130e17bb7b2f0c0472dcc01c1ff6f600ca1a6f9f86a90982df56c4bf43776824": {
"tipLength": 456,
"lastModified": "2021-05-12T22:16:14.249567+00:00",
"source": "user",
"status": {"markedBad": False},
"uri": "opentrons/opentrons_96_filtertiprack_200ul/1",
},
}

io.save_to_file(config.get_tip_length_cal_path(), "pipette1234", data)
result = tip_lengths_for_pipette("pipette1234")
assert len(result) == 1
assert (
result[LabwareUri("opentrons/opentrons_96_filtertiprack_200ul/1")].tipLength
== 456
)
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,36 @@ def test_deck_conflicts_for_96_ch_a12_column_configuration() -> None:
instrument.dispense(50, accessible_plate.wells_by_name()["A1"])


@pytest.mark.ot3_only
def test_close_shave_deck_conflicts_for_96_ch_a12_column_configuration() -> None:
"""Shouldn't raise errors for "almost collision"s."""
protocol_context = simulate.get_protocol_api(version="2.16", robot_type="Flex")
res12 = protocol_context.load_labware("nest_12_reservoir_15ml", "C3")

# Mag block and tiprack adapter are very close to the destination reservoir labware
protocol_context.load_module("magneticBlockV1", "D2")
protocol_context.load_labware(
"opentrons_flex_96_tiprack_200ul",
"B3",
adapter="opentrons_flex_96_tiprack_adapter",
)
tiprack_8 = protocol_context.load_labware("opentrons_flex_96_tiprack_200ul", "B2")
hs = protocol_context.load_module("heaterShakerModuleV1", "D1")
hs_adapter = hs.load_adapter("opentrons_96_deep_well_adapter")
deepwell = hs_adapter.load_labware("nest_96_wellplate_2ml_deep")
protocol_context.load_trash_bin("A3")
p1000_96 = protocol_context.load_instrument("flex_96channel_1000")
p1000_96.configure_nozzle_layout(style=COLUMN, start="A12", tip_racks=[tiprack_8])

hs.close_labware_latch() # type: ignore[union-attr]
p1000_96.distribute(
15,
res12.wells()[0],
deepwell.rows()[0],
disposal_vol=0,
)


@pytest.mark.ot3_only
def test_deck_conflicts_for_96_ch_a1_column_configuration() -> None:
"""It should raise errors for expected deck conflicts."""
Expand Down
10 changes: 10 additions & 0 deletions app-shell/build/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ log][]. For a list of currently known issues, please see the [Opentrons issue tr

---

## Opentrons App Changes in 7.2.1

Welcome to the v7.2.1 release of the Opentrons App!

### Bug Fixes

- Fixed a memory leak that could cause the app to crash.

---

## Opentrons App Changes in 7.2.0

Welcome to the v7.2.0 release of the Opentrons App!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function SetupGripperCalibrationItem({
gripperData,
runId,
}: SetupGripperCalibrationItemProps): JSX.Element | null {
const { t } = useTranslation('protocol_setup')
const { t, i18n } = useTranslation('protocol_setup')

Check warning on line 29 in app/src/organisms/Devices/ProtocolRun/SetupGripperCalibrationItem.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/organisms/Devices/ProtocolRun/SetupGripperCalibrationItem.tsx#L29

Added line #L29 was not covered by tests
const [
openWizardFlowType,
setOpenWizardFlowType,
Expand All @@ -47,7 +47,7 @@ export function SetupGripperCalibrationItem({
setOpenWizardFlowType(GRIPPER_FLOW_TYPES.ATTACH)
}}
>
{t('attach_gripper')}
{i18n.format(t('attach_gripper'), 'capitalize')}
</TertiaryButton>
</Flex>
)
Expand Down
56 changes: 43 additions & 13 deletions app/src/redux/shell/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,50 @@ export function appShellRequestor<Data>(
return remote.ipcRenderer.invoke('usb:request', configProxy)
}

export function appShellListener(
hostname: string | null,
topic: NotifyTopic,
interface CallbackStore {

Check warning on line 37 in app/src/redux/shell/remote.ts

View check run for this annotation

Codecov / codecov/patch

app/src/redux/shell/remote.ts#L37

Added line #L37 was not covered by tests
[hostname: string]: {
[topic in NotifyTopic]: Array<(data: NotifyResponseData) => void>
}
}
const callbackStore: CallbackStore = {}

interface AppShellListener {
hostname: string

Check warning on line 45 in app/src/redux/shell/remote.ts

View check run for this annotation

Codecov / codecov/patch

app/src/redux/shell/remote.ts#L45

Added line #L45 was not covered by tests
topic: NotifyTopic
callback: (data: NotifyResponseData) => void
): void {
remote.ipcRenderer.on(
'notify',
(_, shellHostname, shellTopic, shellMessage) => {
if (
hostname === shellHostname &&
(topic === shellTopic || shellTopic === 'ALL_TOPICS')
) {
callback(shellMessage)
isDismounting?: boolean
}
export function appShellListener({
hostname,
topic,
callback,
isDismounting = false,
}: AppShellListener): CallbackStore {
if (isDismounting) {
const callbacks = callbackStore[hostname]?.[topic]
if (callbacks != null) {
callbackStore[hostname][topic] = callbacks.filter(cb => cb !== callback)
if (!callbackStore[hostname][topic].length) {

Check warning on line 60 in app/src/redux/shell/remote.ts

View check run for this annotation

Codecov / codecov/patch

app/src/redux/shell/remote.ts#L60

Added line #L60 was not covered by tests
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete callbackStore[hostname][topic]

Check warning on line 62 in app/src/redux/shell/remote.ts

View check run for this annotation

Codecov / codecov/patch

app/src/redux/shell/remote.ts#L62

Added line #L62 was not covered by tests
if (!Object.keys(callbackStore[hostname]).length) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete callbackStore[hostname]

Check warning on line 65 in app/src/redux/shell/remote.ts

View check run for this annotation

Codecov / codecov/patch

app/src/redux/shell/remote.ts#L65

Added line #L65 was not covered by tests
}
}
}
)
} else {
callbackStore[hostname] = callbackStore[hostname] ?? {}
callbackStore[hostname][topic] ??= []
callbackStore[hostname][topic].push(callback)
}
return callbackStore

Check warning on line 74 in app/src/redux/shell/remote.ts

View check run for this annotation

Codecov / codecov/patch

app/src/redux/shell/remote.ts#L74

Added line #L74 was not covered by tests
}

// Instantiate the notify listener at runtime.

Check warning on line 77 in app/src/redux/shell/remote.ts

View check run for this annotation

Codecov / codecov/patch

app/src/redux/shell/remote.ts#L77

Added line #L77 was not covered by tests
remote.ipcRenderer.on(
'notify',
(_, shellHostname, shellTopic, shellMessage) => {
callbackStore[shellHostname]?.[shellTopic]?.forEach(cb => cb(shellMessage))

Check warning on line 81 in app/src/redux/shell/remote.ts

View check run for this annotation

Codecov / codecov/patch

app/src/redux/shell/remote.ts#L81

Added line #L81 was not covered by tests
}
)
20 changes: 10 additions & 10 deletions app/src/redux/shell/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ export interface Remote {
ipcRenderer: {
invoke: (channel: string, ...args: unknown[]) => Promise<any>
send: (channel: string, ...args: unknown[]) => void
on: (
channel: string,
listener: (
event: IpcMainEvent,
hostname: string,
topic: NotifyTopic,
message: NotifyResponseData | NotifyNetworkError,
...args: unknown[]
) => void
) => void
on: (channel: string, listener: IpcListener) => void
off: (channel: string, listener: IpcListener) => void
}
}

export type IpcListener = (
event: IpcMainEvent,
hostname: string,
topic: NotifyTopic,
message: NotifyResponseData | NotifyNetworkError,
...args: unknown[]
) => void

export interface NotifyRefetchData {
refetchUsingHTTP: boolean
}
Expand Down
Loading

0 comments on commit 5736292

Please sign in to comment.