Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore: 7.1.0 mergeback into edge from Alpha.7 #14198

Merged
merged 55 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
3402168
docs(api): docstring for `load_trash_bin()` (#14108)
ecormany Dec 7, 2023
28ede47
docs(api): edit docstring for `configure_nozzle_layout` (#13997)
ecormany Dec 7, 2023
9c008f2
fix(app): add deck config query refetch interval (#14122)
brenthagen Dec 7, 2023
93c879a
fix(app): fix styling of read-only deck configurator (#14116)
ncdiehl11 Dec 7, 2023
72255ab
feat(app): add deck config option to device reset (#14131)
koji Dec 7, 2023
7e765eb
refactor(app): verify probe presence before pipette cal, module cal, …
ahiuchingau Dec 7, 2023
5141a35
refactor(shared-data): update pipetteNameSpecs flowRates (#14137)
jerader Dec 7, 2023
2175e9b
fix(api): remove fixed trash restriction from PAPI deck conflict chec…
jbleon95 Dec 7, 2023
bd38336
feat(components): adapt deck location select for OT-2 deck definition…
brenthagen Dec 7, 2023
7677f9a
fix(app, api): expose failOnNotHomed parameter for save_position comm…
mjhuff Dec 7, 2023
281f88c
fix(shared-data, app): fix deck view diff between deck and labware de…
koji Dec 7, 2023
b84c5ec
fix(api): Remove experimental `with_staging_area_slot_d4` parameter (…
SyntaxColoring Dec 7, 2023
cc212b2
fix(app): filter lpc labware combos for single slots (#14120)
brenthagen Dec 7, 2023
e0ac68e
test(api): Add integration tests for new trash APIs (#14142)
SyntaxColoring Dec 7, 2023
990b4ac
refactor(api): Delete unused hard-coded waste chute dimensions (#14147)
SyntaxColoring Dec 8, 2023
faf7b51
fix(api-client): update initial loaded labware to support addressable…
mjhuff Dec 8, 2023
6b8f9b3
fix(app): add resolve button for location conflicts with modules (#14…
jerader Dec 8, 2023
f95f9fa
fix(engine): better and correct errors in addressable area store and …
jbleon95 Dec 8, 2023
bfd1420
docs(api): more partial tip pickup docstrings (#14132)
ecormany Dec 8, 2023
8869fa1
fix(app): moveLabware command text support for waste chute (#14153)
jerader Dec 8, 2023
2053ac9
fix(app): Remove probe check from module calibration (#14154)
sfoster1 Dec 8, 2023
d26e72b
fix(api): clean up tip motor distance caching/usage (#14156)
fsinapi Dec 8, 2023
d5d3968
fix(api): OT2 fixed trash load fix and API 2,15 support for new trash…
CaseyBatten Dec 8, 2023
f7caea6
fix(app): properly display no liquids used text in protocol details/s…
ncdiehl11 Dec 11, 2023
6f14276
feat(app): disable robot overflow menu when robot has an existing mai…
shlokamin Dec 11, 2023
11d65b3
fix(app): case insensitively sort labware list (#14165)
shlokamin Dec 11, 2023
018d7bc
feat(app): resolve single slot location conflict (#14158)
brenthagen Dec 11, 2023
3a93fc4
fix(app): fix module calibration selection slot issue (#14168)
koji Dec 11, 2023
40b2e4f
refactor(hardware-testing): Account for InstrumentContext.trash_conta…
SyntaxColoring Dec 12, 2023
9a3209a
fix(app): prevent terminalrunbanner from closing the current run (#14…
mjhuff Dec 12, 2023
f813b79
fix(app): Copy fixes in app flows (#14167)
smb2268 Dec 12, 2023
98d98f7
refactor(app): special case 96-channel pipette mount text in drop tip…
mjhuff Dec 12, 2023
e7ce57a
fix(components): provide OT-2 fixed trash dimensions for deck locatio…
brenthagen Dec 12, 2023
4ab2000
style(app): hs calibration module cal fixes (#14176)
mjhuff Dec 12, 2023
8134602
docs(api): update Deck Slots page for new features in Python API 2.16…
ecormany Dec 12, 2023
4442886
fix(app-shell,usb-bridge): improve flex usb communication (#14170)
sfoster1 Dec 12, 2023
ec7bb74
feat(api): add tiprack adapter check for 96 ch tip pickup and return …
sanni-t Dec 12, 2023
a859387
fix(protocol-engine): Fix `blowOutInPlace` and `aspirateInPlace` not …
SyntaxColoring Dec 12, 2023
1a81b72
feat(app): add resolve button to protocol setup fixture table (#14181)
brenthagen Dec 12, 2023
ec23072
fix(app): make sure pipette flow wizard title and results copy is cor…
smb2268 Dec 12, 2023
683b2a1
fix(app): fix dqa ODD protocol setup deck config (#14169)
koji Dec 12, 2023
3005cd4
fix display name in deck conflict error message, add separate display…
sanni-t Dec 12, 2023
165a607
refactor(api, robot-server): removal of automatic drop tip from Flex …
CaseyBatten Dec 12, 2023
b1d774d
fix(api): Fix RuntimeError when calling InstrumentContext.type on a 9…
SyntaxColoring Dec 13, 2023
59c1042
fix(app): check firmware update status in instrument cards (#14110)
smb2268 Dec 13, 2023
4c76d5f
fix(api): use adapter labwareId when checking adapter quirk (#14191)
sanni-t Dec 13, 2023
fcbe7f5
fix(app): fix module calibration wizard copy (#14192)
koji Dec 13, 2023
0832c04
fix(components): fix storybook deck configurator (#14038)
koji Dec 13, 2023
76bf6b8
docs(api): new section for `push_out` (#14183)
ecormany Dec 13, 2023
f440726
fix(app): update error messaging for module calibration (#14195)
smb2268 Dec 13, 2023
0e024a8
feat(engine, api): alternate tip drop location for addressable area t…
jbleon95 Dec 13, 2023
9303b02
feat(app, step-generation, shared-data): FE support for moveToAddress…
jerader Dec 13, 2023
f5c4dc2
docs(shared-data): Tidy up addressable area and fixture field descrip…
SyntaxColoring Dec 13, 2023
c08937e
fix(app): allow ODD to poll for completed analysis on protocol setup …
b-cooper Dec 14, 2023
59b436a
Merge branch 'edge' into chore_7.1.0-mergeback-into-edge
sfoster1 Dec 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion api-client/src/protocols/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ describe('parseInitialLoadedLabwareByAdapter', () => {
})
})
describe('parseInitialLoadedLabwareBySlot', () => {
it('returns only labware loaded in slots', () => {
it('returns labware loaded in slots', () => {
const expected = {
2: mockRunTimeCommands.find(
c =>
Expand All @@ -282,6 +282,48 @@ describe('parseInitialLoadedLabwareBySlot', () => {
expected
)
})
it('returns labware loaded in addressable areas', () => {
const mockAddressableAreaLoadedLabwareCommand = ([
{
id: 'commands.LOAD_LABWARE-3',
createdAt: '2022-04-01T15:46:01.745870+00:00',
commandType: 'loadLabware',
key: 'commands.LOAD_LABWARE-3',
status: 'succeeded',
params: {
location: {
addressableAreaName: 'D4',
},
loadName: 'nest_96_wellplate_100ul_pcr_full_skirt',
namespace: 'opentrons',
version: 1,
labwareId: null,
displayName: 'NEST 96 Well Plate 100 µL PCR Full Skirt',
},
result: {
labwareId: 'labware-3',
definition: {},
offsetId: null,
},
error: null,
startedAt: '2022-04-01T15:46:01.745870+00:00',
completedAt: '2022-04-01T15:46:01.745870+00:00',
},
] as any) as RunTimeCommand[]

const expected = {
D4: mockAddressableAreaLoadedLabwareCommand.find(
c =>
c.commandType === 'loadLabware' &&
typeof c.params.location === 'object' &&
'addressableAreaName' in c.params?.location &&
c.params?.location?.addressableAreaName === 'D4'
),
}
expect(
parseInitialLoadedLabwareBySlot(mockAddressableAreaLoadedLabwareCommand)
).toEqual(expected)
})
})
describe('parseInitialLoadedLabwareByModuleId', () => {
it('returns only labware loaded in modules', () => {
Expand Down
13 changes: 8 additions & 5 deletions api-client/src/protocols/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
export function parseInitialPipetteNamesByMount(
commands: RunTimeCommand[]
): PipetteNamesByMount {
const rightPipetteName = commands.find(

Check warning on line 28 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 28 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 28 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
(command): command is LoadPipetteRunTimeCommand =>
command.commandType === 'loadPipette' && command.params.mount === 'right'
)?.params.pipetteName as PipetteName | undefined
const leftPipetteName = commands.find(

Check warning on line 32 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 32 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 32 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
(command): command is LoadPipetteRunTimeCommand =>
command.commandType === 'loadPipette' && command.params.mount === 'left'
)?.params.pipetteName as PipetteName | undefined
Expand Down Expand Up @@ -117,11 +117,14 @@
return reduce<LoadLabwareRunTimeCommand, LoadedLabwareBySlot>(
loadLabwareCommandsReversed,
(acc, command) => {
if (
typeof command.params.location === 'object' &&
'slotName' in command.params.location
) {
return { ...acc, [command.params.location.slotName]: command }
if (typeof command.params.location === 'object') {
let slot: string
if ('slotName' in command.params.location) {
slot = command.params.location.slotName
} else if ('addressableAreaName' in command.params.location) {
slot = command.params.location.addressableAreaName
} else return acc
return { ...acc, [slot]: command }
} else {
return acc
}
Expand Down
225 changes: 0 additions & 225 deletions api/docs/img/Flex-and-OT-2-decks.svg

This file was deleted.

94 changes: 94 additions & 0 deletions api/docs/img/OT-2-deck.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
477 changes: 477 additions & 0 deletions api/docs/img/flex-deck.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 19 additions & 2 deletions api/docs/v2/basic_commands/liquids.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,27 @@ Flex and OT-2 pipettes dispense at :ref:`default flow rates <new-plunger-flow-ra

pipette.dispense(200, plate['B1'], rate=2.0)

.. Removing the 2 notes here from the original. Covered by new revisions.

.. versionadded:: 2.0

.. _push-out-dispense:

Push Out After Dispense
-----------------------

The optional ``push_out`` parameter of ``dispense()`` helps ensure all liquid leaves the tip. Use ``push_out`` for applications that require moving the pipette plunger lower than the default, without performing a full :ref:`blow out <blow-out>`.

For example, this dispense action moves the plunger the equivalent of an additional 5 µL beyond where it would stop if ``push_out`` was set to zero or omitted::

pipette.pick_up_tip()
pipette.aspirate(100, plate['A1'])
pipette.dispense(100, plate['B1'], push_out=5)
pipette.drop_tip()

.. versionadded:: 2.15

.. note::
In version 7.0.2 and earlier of the robot software, you could accomplish a similar result by dispensing a volume greater than what was aspirated into the pipette. In version 7.1.0 and later, the API will return an error. Calculate the difference between the two amounts and use that as the value of ``push_out``.

.. _new-blow-out:

.. _blow-out:
Expand Down
130 changes: 119 additions & 11 deletions api/docs/v2/deck_slots.rst
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
:og:description: How to specify deck slots in the Python Protocol API.

..
Allow concise cross-referencing to ProtocolContext.load_labware() et. al., without barfing out the whole import path.
.. py:currentmodule:: opentrons.protocol_api


.. _deck-slots:

**********
Deck Slots
**********

When you load an item onto the robot's deck, like with :py:obj:`ProtocolContext.load_labware()` or :py:obj:`ProtocolContext.load_module()`, you need to specify which slot to put it in. The API accepts values that correspond to the physical deck slot labels on an OT-2 or Flex robot.
Deck slots are where you place hardware items on the deck surface of your Opentrons robot. In the API, you load the corresponding items into your protocol with methods like :py:obj:`.ProtocolContext.load_labware`, :py:obj:`.ProtocolContext.load_module`, or :py:obj:`.ProtocolContext.load_trash_bin`. When you call these methods, you need to specify which slot to load the item in.

Physical Deck Labels
====================

The Opentrons Flex uses a coordinate labeling system for slots A1 (back left) through D3 (front right).
Flex uses a coordinate labeling system for slots A1 (back left) through D4 (front right). Columns 1 through 3 are in the *working area* and are accessible by pipettes and the gripper. Column 4 is in the *staging area* and is only accessible by the gripper. For more information on staging area slots, see :ref:`deck-configuration` below.

.. image:: ../img/flex-deck.svg
:width: 80%

The Opentrons OT-2 uses a numeric labeling system for slots 1 (front left) through 11 (back center). The back right slot is occupied by the fixed trash.
OT-2 uses a numeric labeling system for slots 1 (front left) through 11 (back center). The back right slot is occupied by the fixed trash.

.. image:: ../img/Flex-and-OT-2-decks.svg
:width: 100%
.. image:: ../img/OT-2-deck.svg
:width: 55%


API Deck Labels
===============

Specify a slot in either the Flex or OT-2 format:
The API accepts values that correspond to the physical deck slot labels on a Flex or OT-2 robot. Specify a slot in either format:

* A coordinate like ``"A1"``. This format must be a string.
* A number like ``"10"`` or ``10``. This format can be a string or an integer.
Expand Down Expand Up @@ -83,3 +81,113 @@ The correspondence between deck labels is based on the relative locations of the
- 3

.. TODO staging slots and error handling of A4–D4 in OT-2 protocols

Slots A4, B4, C4, and D4 on Flex have no equivalent on OT-2.

.. _deck-configuration:

Deck Configuration
==================

A Flex running robot system version 7.1.0 or higher lets you specify its deck configuration on the touchscreen or in the Opentrons App. This tells the robot the positions of unpowered *deck fixtures*: items that replace standard deck slots. The following table lists currently supported deck fixtures and their allowed deck locations.

.. list-table::
:header-rows: 1

* - Fixture
- Slots
* - Staging area slots
- A3–D3
* - Trash bin
- A1–D1, A3-D3
* - Waste chute
- D3

Which fixtures you need to configure depend on both load methods and the effects of other methods called in your protocol. The following sections explain how to configure each type of fixture.

.. _configure-staging-area-slots:

Staging Area Slots
------------------

Slots A4 through D4 are the staging area slots. Pipettes can't reach the staging area, but these slots are always available in the API for loading and moving labware. Using a slot in column 4 as the ``location`` argument of :py:meth:`.load_labware` or the ``new_location`` argument of :py:meth:`.move_labware` will require the corresponding staging area slot in the robot's deck configuration::

plate_1 = protocol.load_labware(
load_name="corning_96_wellplate_360ul_flat", location="C3"
) # no staging slots required
plate_2 = protocol.load_labware(
load_name="corning_96_wellplate_360ul_flat", location="D4"
) # one staging slot required
protocol.move_labware(
labware=plate_1, new_location="C4"
) # two staging slots required

.. versionadded:: 2.16

Since staging area slots also include a standard deck slot in column 3, they are physically incompatible with powered modules in the same row of column 3. For example, if you try to load a module in C3 and labware in C4, the API will raise an error::

temp_mod = protocol.load_module(
module_name="temperature module gen2",
location="C3"
)
staging_plate = protocol.load_labware(
load_name="corning_96_wellplate_360ul_flat", location="C4"
) # deck conflict error

It is possible to use slot D4 along with the waste chute. See the :ref:`Waste Chute <configure-waste-chute>` section below for details.

.. _configure-trash-bin:

Trash Bin
---------

In version 2.15 of the API, Flex can only have a single trash bin in slot A3. You do not have to (and cannot) load the trash in version 2.15 protocols.

Starting in API version 2.16, you must load trash bin fixtures in your protocol in order to use them. Use :py:meth:`.load_trash_bin` to load a movable trash bin. This example loads a single bin in the default location::

default_trash = protocol.load_trash_bin(location = "A3")

.. versionadded:: 2.16

.. note::
The :py:class:`TrashBin` class doesn't have any callable methods, so you don't have to save the result of ``load_trash_bin()`` to a variable, especially if your protocol only loads a single trash container. Being able to reference the trash bin by name is useful when dealing with multiple trash containers.

Call ``load_trash_bin()`` multiple times to add more than one bin. See :ref:`Adding a Trash Container <pipette-trash-container>` for more information on using pipettes with multiple trash bins.

.. _configure-waste-chute:

Waste Chute
-----------

The waste chute accepts various materials from Flex pipettes or the Flex Gripper and uses gravity to transport them outside of the robot for disposal. Pipettes can dispose of liquid or drop tips into the chute. The gripper can drop tip racks and other labware into the chute.

To use the waste chute, first use :py:meth:`.load_waste_chute` to load it in slot D3::

chute = protocol.load_waste_chute()

.. versionadded:: 2.16

The ``load_waste_chute()`` method takes no arguments, since D3 is the only valid location for the chute. However, there are multiple variant configurations of the waste chute, depending on how other methods in your protocol use it.

The waste chute is installed either on a standard deck plate adapter or on a deck plate adapter with a staging area. If any :py:meth:`.load_labware` or :py:meth:`.move_labware` calls in your protocol reference slot D4, you have to use the deck plate adapter with staging area.

The waste chute has a removable cover with a narrow opening which helps prevent aerosols and droplets from contaminating the working area. 1- and 8-channel pipettes can dispense liquid, blow out, or drop tips through the opening in the cover. Any of the following require you to remove the cover.

- :py:meth:`.dispense`, :py:meth:`.blow_out`, or :py:meth:`.drop_tip` with a 96-channel pipette.
- :py:meth:`.move_labware` with the chute as ``new_location`` and ``use_gripper=True``.

If your protocol *does not* call any of these methods, your deck configuration should include the cover.

In total, there are four possible deck configurations for the waste chute.
- Waste chute only
- Waste chute with cover
- Waste chute with staging area slot
- Waste chute with staging area slot and cover

Deck Conflicts
==============

A deck conflict check occurs when preparing to run a Python protocol on a Flex running robot system version 7.1.0 or higher. The Opentrons App and touchscreen will prevent you from starting the protocol run until any conflicts are resolved. You can resolve them one of two ways:

- Physically move hardware around the deck, and update the deck configuration.
- Alter your protocol to work with the current deck configuration, and resend the protocol to your Flex.
40 changes: 26 additions & 14 deletions api/src/opentrons/hardware_control/backends/ot3controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,13 +735,19 @@ async def home_tip_motors(
if not self._feature_flags.stall_detection_enabled
else False,
)
positions = await runner.run(can_messenger=self._messenger)
if NodeId.pipette_left in positions:
self._gear_motor_position = {
NodeId.pipette_left: positions[NodeId.pipette_left].motor_position
}
else:
log.debug("no position returned from NodeId.pipette_left")
try:
positions = await runner.run(can_messenger=self._messenger)
if NodeId.pipette_left in positions:
self._gear_motor_position = {
NodeId.pipette_left: positions[NodeId.pipette_left].motor_position
}
else:
log.debug("no position returned from NodeId.pipette_left")
self._gear_motor_position = {}
except Exception as e:
log.error("Clearing tip motor position due to failed movement")
self._gear_motor_position = {}
raise e

async def tip_action(
self,
Expand All @@ -755,13 +761,19 @@ async def tip_action(
if not self._feature_flags.stall_detection_enabled
else False,
)
positions = await runner.run(can_messenger=self._messenger)
if NodeId.pipette_left in positions:
self._gear_motor_position = {
NodeId.pipette_left: positions[NodeId.pipette_left].motor_position
}
else:
log.debug("no position returned from NodeId.pipette_left")
try:
positions = await runner.run(can_messenger=self._messenger)
if NodeId.pipette_left in positions:
self._gear_motor_position = {
NodeId.pipette_left: positions[NodeId.pipette_left].motor_position
}
else:
log.debug("no position returned from NodeId.pipette_left")
self._gear_motor_position = {}
except Exception as e:
log.error("Clearing tip motor position due to failed movement")
self._gear_motor_position = {}
raise e

@requires_update
@requires_estop
Expand Down
40 changes: 27 additions & 13 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -907,10 +907,11 @@ async def home_gear_motors(self) -> None:
GantryLoad.HIGH_THROUGHPUT
][OT3AxisKind.Q]

max_distance = self._backend.axis_bounds[Axis.Q][1]
# if position is not known, move toward limit switch at a constant velocity
if not any(self._backend.gear_motor_position):
if len(self._backend.gear_motor_position.keys()) == 0:
await self._backend.home_tip_motors(
distance=self._backend.axis_bounds[Axis.Q][1],
distance=max_distance,
velocity=homing_velocity,
)
return
Expand All @@ -919,7 +920,13 @@ async def home_gear_motors(self) -> None:
Axis.P_L
]

if current_pos_float > self._config.safe_home_distance:
# We filter out a distance more than `max_distance` because, if the tip motor was stopped during
# a slow-home motion, the position may be stuck at an enormous large value.
if (
current_pos_float > self._config.safe_home_distance
and current_pos_float < max_distance
):

fast_home_moves = self._build_moves(
{Axis.Q: current_pos_float}, {Axis.Q: self._config.safe_home_distance}
)
Expand All @@ -933,7 +940,9 @@ async def home_gear_motors(self) -> None:

# move until the limit switch is triggered, with no acceleration
await self._backend.home_tip_motors(
distance=(current_pos_float + self._config.safe_home_distance),
distance=min(
current_pos_float + self._config.safe_home_distance, max_distance
),
velocity=homing_velocity,
)

Expand Down Expand Up @@ -2002,15 +2011,20 @@ async def get_tip_presence_status(
self,
mount: Union[top_types.Mount, OT3Mount],
) -> TipStateType:
real_mount = OT3Mount.from_mount(mount)
async with contextlib.AsyncExitStack() as stack:
if (
real_mount == OT3Mount.LEFT
and self._gantry_load == GantryLoad.HIGH_THROUGHPUT
):
await stack.enter_async_context(self._high_throughput_check_tip())
result = await self._backend.get_tip_status(real_mount)
return result
"""
Check tip presence status. If a high throughput pipette is present,
move the tip motors down before checking the sensor status.
"""
async with self._motion_lock:
real_mount = OT3Mount.from_mount(mount)
async with contextlib.AsyncExitStack() as stack:
if (
real_mount == OT3Mount.LEFT
and self._gantry_load == GantryLoad.HIGH_THROUGHPUT
):
await stack.enter_async_context(self._high_throughput_check_tip())
result = await self._backend.get_tip_status(real_mount)
return result

async def verify_tip_presence(
self, mount: Union[top_types.Mount, OT3Mount], expected: TipStateType
Expand Down
Loading
Loading