def test_labware_mappings(loop, monkeypatch):
    lw_name, lw_label = None, None

    def fake_ctx_load(labware_name, location, label=None):
        nonlocal lw_name
        nonlocal lw_label
        lw_name = labware_name
        lw_label = label
        return 'heres a fake labware'

    ctx = ProtocolContext(loop)
    bc = back_compat.BCLabware(ctx)
    monkeypatch.setattr(ctx, 'load_labware', fake_ctx_load)
    obj = bc.load('384-plate', 2, 'hey there')
    assert obj == 'heres a fake labware'
    assert lw_name == 'corning_384_wellplate_112ul_flat'
    assert lw_label == 'hey there'

    with pytest.raises(NotImplementedError,
                       match='Labware 24-vial-rack is not supported'):
        bc.load('24-vial-rack', 2)

    with pytest.raises(NotImplementedError,
                       match='Module load not yet implemented'):
        bc.load('magdeck', 3)
Example #2
0
def test_proto_with_exception(ensure_api2, loop):
    ctx = ProtocolContext(loop)
    exc_in_root = '''
def run(ctx):
    raise Exception("hi")
'''
    comped = compile(exc_in_root, 'test_file.py', 'exec')
    with pytest.raises(execute.ExceptionInProtocolError) as e:
        execute.run_protocol(protocol_code=comped, context=ctx)
    assert 'Exception [line 3]: hi' in str(e.value)

    nested_exc = '''
import ast

def this_throws():
    raise Exception("hi")

def run(ctx):
    this_throws()
'''
    comped = compile(nested_exc, 'nested.py', 'exec')
    with pytest.raises(execute.ExceptionInProtocolError) as e:
        execute.run_protocol(protocol_code=comped, context=ctx)
    assert '[line 5]' in str(e.value)
    assert 'Exception [line 5]: hi' in str(e.value)
Example #3
0
def model(robot, hardware, loop, request):
    # Use with pytest.mark.parametrize(’labware’, [some-labware-name])
    # to have a different labware loaded as .container. If not passed,
    # defaults to the version-appropriate way to do 96 flat
    from opentrons.legacy_api.containers import load
    from opentrons.legacy_api.instruments.pipette import Pipette

    try:
        lw_name = request.getfixturevalue('labware')
    except Exception:
        lw_name = None

    if isinstance(hardware, hc.HardwareAPILike):
        ctx = ProtocolContext(loop=loop, hardware=hardware)
        pip = ctx.load_instrument('p300_single', 'right')
        loop.run_until_complete(
            hardware.cache_instruments({Mount.RIGHT: 'p300_single'}))
        instrument = models.Instrument(pip, context=ctx)
        plate = ctx.load_labware(lw_name or 'corning_96_wellplate_360ul_flat',
                                 1)
        rob = hardware
        container = models.Container(plate, context=ctx)
    else:
        print("hardware is {}".format(hardware))
        pipette = Pipette(robot, ul_per_mm=18.5, max_volume=300, mount='right')
        plate = load(robot, lw_name or '96-flat', '1')
        rob = robot
        instrument = models.Instrument(pipette)
        container = models.Container(plate)

    return namedtuple('model',
                      'robot instrument container')(robot=rob,
                                                    instrument=instrument,
                                                    container=container)
Example #4
0
def test_load_labware(loop):
    ctx = ProtocolContext(loop=loop)
    data = {
        "labware": {
            "sourcePlateId": {
                "slot": "10",
                "model": "usascientific_12_reservoir_22ml",
                "display-name": "Source (Buffer)"
            },
            "destPlateId": {
                "slot": "11",
                "model": "corning_96_wellplate_360ul_flat",
                "display-name": "Destination Plate"
            },
            "oldPlateId": {
                "slot": "9",
                "model": "96-flat",
                "display-name": "Test Plate"
            },
        }
    }
    loaded_labware = execute_v1.load_labware_from_json_loadnames(ctx, data)

    # objects in loaded_labware should be same objs as labware objs in the deck
    assert loaded_labware['sourcePlateId'] == ctx.loaded_labwares[10]
    assert 'Source (Buffer)' in str(loaded_labware['sourcePlateId'])
    assert loaded_labware['destPlateId'] == ctx.loaded_labwares[11]
    assert 'Destination Plate' in str(loaded_labware['destPlateId'])
    assert loaded_labware['oldPlateId'].name == \
        'corning_96_wellplate_360ul_flat'
    assert 'Test Plate' in str(loaded_labware['oldPlateId'])
Example #5
0
    def __init__(self, name, protocol, hardware, loop, broker, motion_lock):
        self._broker = broker
        self._default_logger = self._broker.logger
        self._sim_logger = self._broker.logger.getChild('sim')
        self._run_logger = self._broker.logger.getChild('run')
        self._loop = loop
        self.name = name
        self._protocol = protocol
        self._hardware = hardware
        self._simulating_ctx = ProtocolContext(loop=self._loop,
                                               broker=self._broker)
        self.state = None
        self.commands = []
        self.command_log = {}
        self.errors = []

        self._containers = []
        self._instruments = []
        self._modules = []
        self._interactions = []

        self.instruments = None
        self.containers = None
        self.modules = None
        self.metadata = {}
        self.api_level = None
        self.protocol_text = protocol.text

        self.startTime = None
        self._motion_lock = motion_lock
def test_load_labware_from_json_defs(loop, get_labware_fixture):
    ctx = ProtocolContext(loop=loop)
    custom_trough_def = get_labware_fixture('fixture_12_trough')
    data = {
        "labwareDefinitions": {
            "someTroughDef": custom_trough_def
        },
        "labware": {
            "sourcePlateId": {
                "slot": "10",
                "definitionId": "someTroughDef",
                "displayName": "Source (Buffer)"
            },
            "destPlateId": {
                "slot": "11",
                "definitionId": "someTroughDef"
            },
        }
    }
    loaded_labware = load_labware_from_json_defs(ctx, data)

    # objects in loaded_labware should be same objs as labware objs in the deck
    assert loaded_labware['sourcePlateId'] == ctx.loaded_labwares[10]
    # use the displayName from protocol's labware.labwareId.displayName
    assert 'Source (Buffer)' in str(loaded_labware['sourcePlateId'])
    assert loaded_labware['destPlateId'] == ctx.loaded_labwares[11]
    # use the metadata.displayName from embedded def
    assert (custom_trough_def['metadata']['displayName']
            in str(loaded_labware['destPlateId']))
def test_proto_with_exception(loop):
    ctx = ProtocolContext(loop)
    exc_in_root = '''metadata={"apiLevel": "2.0"}

def run(ctx):
    raise Exception("hi")
'''
    protocol = parse(exc_in_root)
    with pytest.raises(execute_python.ExceptionInProtocolError) as e:
        execute.run_protocol(protocol, context=ctx)
    assert 'Exception [line 4]: hi' in str(e.value)

    nested_exc = '''
import ast

def this_throws():
    raise Exception("hi")

def run(ctx):
    this_throws()

metadata={"apiLevel": "2.0"};
'''
    protocol = parse(nested_exc)
    with pytest.raises(execute_python.ExceptionInProtocolError) as e:
        execute.run_protocol(protocol, context=ctx)
    assert '[line 5]' in str(e.value)
    assert 'Exception [line 5]: hi' in str(e.value)
Example #8
0
def test_proto_with_exception(ensure_api2, loop):
    ctx = ProtocolContext(loop)
    exc_in_root = '''
def run(ctx):
    raise Exception("hi")
'''
    protocol = parse(exc_in_root)
    with pytest.raises(execute.ExceptionInProtocolError) as e:
        execute.run_protocol(protocol, context=ctx)
    assert 'Exception [line 3]: hi' in str(e.value)

    nested_exc = '''
import ast

def this_throws():
    raise Exception("hi")

def run(ctx):
    this_throws()
'''
    protocol = parse(nested_exc)
    with pytest.raises(execute.ExceptionInProtocolError) as e:
        execute.run_protocol(protocol, context=ctx)
    assert '[line 5]' in str(e.value)
    assert 'Exception [line 5]: hi' in str(e.value)
Example #9
0
def test_bad_protocol(loop):
    ctx = ProtocolContext(loop)
    no_run = parse('''
metadata={"apiLevel": "2.0"}
print("hi")
''')
    with pytest.raises(execute.MalformedProtocolError) as e:
        execute.run_protocol(no_run, context=ctx)
        assert "No function 'run" in str(e.value)

    no_args = parse('''
metadata={"apiLevel": "2.0"}
def run():
    pass
''')
    with pytest.raises(execute.MalformedProtocolError) as e:
        execute.run_protocol(no_args, context=ctx)
        assert "Function 'run()' does not take any parameters" in str(e.value)

    many_args = parse('''
metadata={"apiLevel": "2.0"}
def run(a, b):
    pass
''')
    with pytest.raises(execute.MalformedProtocolError) as e:
        execute.run_protocol(many_args, context=ctx)
        assert "must be called with more than one argument" in str(e.value)
def test_papi_execute_json_v3(monkeypatch, loop, get_json_protocol_fixture):
    protocol_data = get_json_protocol_fixture(
        '3', 'testAllAtomicSingleV3', False)
    protocol = parse(protocol_data, None)
    ctx = ProtocolContext(loop=loop)
    ctx.home()
    # Check that we end up executing the protocol ok
    execute.run_protocol(protocol, True, ctx)
def test_papi_execute_json_v4(monkeypatch, loop, get_json_protocol_fixture):
    protocol_data = get_json_protocol_fixture('4', 'testModulesProtocol',
                                              False)
    protocol = parse(protocol_data, None)
    ctx = ProtocolContext(loop=loop)
    ctx.home()
    # Check that we end up executing the protocol ok
    execute.run_protocol(protocol, ctx)
Example #12
0
async def test_load_pipettes(loop, protocol_data):

    ctx = ProtocolContext(loop=loop)

    loaded_pipettes = execute_v1.load_pipettes_from_json(ctx, protocol_data)
    assert 'leftPipetteHere' in loaded_pipettes
    assert len(loaded_pipettes) == 1
    pip = loaded_pipettes['leftPipetteHere']
    assert pip.mount == 'left'
    assert ctx.loaded_instruments['left'] == pip
Example #13
0
def test_bad_protocol(ensure_api2, loop):
    ctx = ProtocolContext(loop)
    with pytest.raises(execute.MalformedProtocolError) as e:
        execute.run_protocol(protocol_code='print("hi")', context=ctx)
        assert "No function 'run" in str(e.value)
    with pytest.raises(execute.MalformedProtocolError) as e:
        execute.run_protocol(protocol_code='def run(): pass', context=ctx)
        assert "Function 'run()' does not take any parameters" in str(e.value)
    with pytest.raises(execute.MalformedProtocolError) as e:
        execute.run_protocol(protocol_code='def run(a, b): pass', context=ctx)
        assert "must be called with more than one argument" in str(e.value)
Example #14
0
def test_dispatch_commands(monkeypatch, loop):
    with open(Path(__file__).parent / 'data' / 'v3_json_dispatch.json',
              'r') as f:
        protocol_data = json.load(f)

    command_log = []
    mock_pipette = MockPipette(command_log)
    insts = {"pipetteId": mock_pipette}

    ctx = ProtocolContext(loop=loop)

    def mock_delay(seconds=0, minutes=0, msg=None):
        command_log.append(("delay", seconds + minutes * 60))

    monkeypatch.setattr(ctx, 'delay', mock_delay)

    source_plate = ctx.load_labware('corning_96_wellplate_360ul_flat', '1')
    dest_plate = ctx.load_labware('corning_96_wellplate_360ul_flat', '2')
    tiprack = ctx.load_labware('opentrons_96_tiprack_10ul', '3')

    loaded_labware = {
        'sourcePlateId': source_plate,
        'destPlateId': dest_plate,
        'tiprackId': tiprack,
        'trashId': ctx.fixed_trash
    }

    execute_v3.dispatch_json(ctx, protocol_data, insts, loaded_labware)

    assert command_log == [
        ("pick_up_tip", (tiprack['B1'], )), ("set: flow_rate.aspirate", (3, )),
        ("set: flow_rate.dispense", (3, )), ("set: flow_rate.blow_out", (3, )),
        ("aspirate", (
            5,
            source_plate['A1'].bottom(2),
        )), ("delay", 42),
        ("set: flow_rate.aspirate", (2.5, )),
        ("set: flow_rate.dispense", (2.5, )),
        ("set: flow_rate.blow_out", (2.5, )),
        ("dispense", (
            4.5,
            dest_plate['B1'].bottom(1),
        )),
        ("touch_tip", (dest_plate['B1'], ), {
            "v_offset": 0.33000000000000007
        }), ("set: flow_rate.aspirate", (2, )),
        ("set: flow_rate.dispense", (2, )), ("set: flow_rate.blow_out", (2, )),
        ("blow_out", (dest_plate['B1'], )),
        ("move_to", (ctx.deck.position_for('5').move(Point(1, 2, 3)), ), {
            "force_direct": None,
            "minimum_z_height": None
        }), ("drop_tip", (ctx.fixed_trash['A1'], ))
    ]
Example #15
0
def build_v2_model(h, lw_name, loop):
    ctx = ProtocolContext(loop=loop, hardware=h)

    loop.run_until_complete(h.cache_instruments({Mount.RIGHT: 'p300_single'}))
    tiprack = ctx.load_labware('opentrons_96_tiprack_300ul', '2')
    pip = ctx.load_instrument('p300_single', 'right', tip_racks=[tiprack])
    instrument = models.Instrument(pip, context=ctx)
    plate = ctx.load_labware(lw_name or 'corning_96_wellplate_360ul_flat', 1)
    container = models.Container(plate, context=ctx)
    return namedtuple('model', 'robot instrument container')(
        robot=h,
        instrument=instrument,
        container=container,
    )
Example #16
0
    def _run(self):
        def on_command(message):
            if message['$'] == 'before':
                self.log_append()
            if message['name'] == command_types.PAUSE:
                self.set_state('paused')
            if message['name'] == command_types.RESUME:
                self.set_state('running')

        self._reset()

        _unsubscribe = self._broker.subscribe(command_types.COMMAND,
                                              on_command)

        self.startTime = now()
        self.set_state('running')

        try:
            self.resume()
            self._pre_run_hooks()
            if ff.use_protocol_api_v2():
                bundled_data = None
                bundled_labware = None
                if isinstance(self._protocol, PythonProtocol):
                    bundled_data = self._protocol.bundled_data
                    bundled_labware = self._protocol.bundled_labware
                self._hardware.cache_instruments()
                ctx = ProtocolContext(loop=self._loop,
                                      broker=self._broker,
                                      bundled_labware=bundled_labware,
                                      bundled_data=bundled_data)
                ctx.connect(self._hardware)
                ctx.home()
                run_protocol(self._protocol, context=ctx)
            else:
                self._hardware.broker = self._broker
                if isinstance(self._protocol, JsonProtocol):
                    execute_protocol(self._protocol)
                else:
                    exec(self._protocol.contents, {})
            self.set_state('finished')
            self._hardware.home()
        except Exception as e:
            log.exception("Exception during run:")
            self.error_append(e)
            self.set_state('error')
            raise e
        finally:
            _unsubscribe()
Example #17
0
def test_dispatch_commands(monkeypatch, loop):
    with open(
            os.path.join(os.path.dirname(__file__), 'data',
                         'v1_json_dispatch.json'), 'r') as f:
        protocol_data = json.load(f)
    ctx = ProtocolContext(loop=loop)
    cmd = []
    flow_rates = []

    def mock_sleep(minutes=0, seconds=0, msg=None):
        cmd.append(("sleep", minutes * 60 + seconds))

    def mock_aspirate(volume, location):
        cmd.append(("aspirate", volume, location))

    def mock_dispense(volume, location):
        cmd.append(("dispense", volume, location))

    def mock_set_flow_rate(mount, aspirate=None, dispense=None):
        flow_rates.append((aspirate, dispense))

    insts = execute_v1.load_pipettes_from_json(ctx, protocol_data)

    source_plate = ctx.load_labware('corning_96_wellplate_360ul_flat', '1')
    dest_plate = ctx.load_labware('corning_96_wellplate_360ul_flat', '2')

    loaded_labware = {'sourcePlateId': source_plate, 'destPlateId': dest_plate}
    pipette = insts['pipetteId']
    monkeypatch.setattr(pipette, 'aspirate', mock_aspirate)
    monkeypatch.setattr(pipette, 'dispense', mock_dispense)
    monkeypatch.setattr(ctx._hw_manager.hardware._api, 'set_flow_rate',
                        mock_set_flow_rate)
    monkeypatch.setattr(ctx, 'delay', mock_sleep)

    execute_v1.dispatch_json(ctx, protocol_data, insts, loaded_labware)

    assert cmd == [("aspirate", 5, source_plate['A1'].bottom()), ("sleep", 42),
                   ("dispense", 4.5, dest_plate['B1'].bottom())]

    assert flow_rates == [
        (123, None),
        (None, 102),
        (101, None),
        (None, 102),
    ]
Example #18
0
def test_get_location_with_offset(loop, command_type):
    ctx = ProtocolContext(loop=loop)
    plate = ctx.load_labware("corning_96_wellplate_360ul_flat", 1)
    well = "B2"

    default_values = {
        'aspirate-mm-from-bottom': 2,
        'dispense-mm-from-bottom': 3
    }

    loaded_labware = {"someLabwareId": plate}

    # test with nonzero and with zero command-specific offset
    for offset in [5, 0]:
        command_params = {
            "labware": "someLabwareId",
            "well": well,
            "offsetFromBottomMm": offset
        }
        offs = execute_v1._get_bottom_offset(command_type, command_params,
                                             default_values)
        assert offs == offset
        result = execute_v1._get_location_with_offset(loaded_labware,
                                                      command_type,
                                                      command_params,
                                                      default_values)
        assert result.labware == plate[well]
        assert result.point\
            == plate[well].bottom().point + Point(z=offset)

    command_params = {"labware": "someLabwareId", "well": well}

    # no command-specific offset, use default
    result = execute_v1._get_location_with_offset(loaded_labware, command_type,
                                                  command_params,
                                                  default_values)
    default = default_values['{}-mm-from-bottom'.format(command_type)]
    assert execute_v1._get_bottom_offset(command_type, command_params,
                                         default_values) == default
    assert result.point\
        == plate[well].bottom().point + Point(z=default)
Example #19
0
def test_execute_ok(protocol, protocol_file, ensure_api2, loop):
    proto = compile(protocol.text, protocol.filename, 'exec')
    ctx = ProtocolContext(loop)
    execute.run_protocol(protocol_code=proto, context=ctx)
Example #20
0
def apiv2_singletons_factory(virtual_smoothie_env):
    from opentrons.protocol_api.legacy_wrapper import api
    ctx = ProtocolContext()
    return {**api.build_globals(ctx)}
Example #21
0
def test_load_labware_trash(loop):
    ctx = ProtocolContext(loop=loop)
    data = {"labware": {"someTrashId": {"slot": "12", "model": "fixed-trash"}}}
    result = execute_v1.load_labware_from_json_loadnames(ctx, data)

    assert result['someTrashId'] == ctx.fixed_trash
Example #22
0
    def _simulate(self):
        self._reset()

        stack = []
        res = []
        commands = []

        self._containers.clear()
        self._instruments.clear()
        self._modules.clear()
        self._interactions.clear()

        def on_command(message):
            payload = message['payload']
            description = payload.get('text', '').format(**payload)

            if message['$'] == 'before':
                level = len(stack)

                stack.append(message)
                commands.append(payload)

                res.append({
                    'level': level,
                    'description': description,
                    'id': len(res)
                })
            else:
                stack.pop()

        unsubscribe = self._broker.subscribe(command_types.COMMAND, on_command)

        try:
            # ensure actual pipettes are cached before driver is disconnected
            if ff.use_protocol_api_v2():
                self._hardware.cache_instruments()
                instrs = {}
                for mount, pip in self._hardware.attached_instruments.items():
                    if pip:
                        instrs[mount] = {
                            'model': pip['model'],
                            'id': pip.get('pipette_id', '')
                        }
                sim = adapters.SynchronousAdapter.build(
                    API.build_hardware_simulator,
                    instrs, [
                        mod.name()
                        for mod in self._hardware.attached_modules.values()
                    ],
                    strict_attached_instruments=False)
                sim.home()
                bundled_data = None
                bundled_labware = None
                if isinstance(self._protocol, PythonProtocol):
                    bundled_data = self._protocol.bundled_data
                    bundled_labware = self._protocol.bundled_labware
                self._simulating_ctx = ProtocolContext(
                    loop=self._loop,
                    hardware=sim,
                    broker=self._broker,
                    bundled_labware=bundled_labware,
                    bundled_data=bundled_data)
                run_protocol(self._protocol,
                             simulate=True,
                             context=self._simulating_ctx)
            else:
                self._hardware.broker = self._broker
                self._hardware.cache_instrument_models()
                self._hardware.disconnect()
                if isinstance(self._protocol, JsonProtocol):
                    execute_protocol(self._protocol)
                else:
                    exec(self._protocol.contents, {})
        finally:
            # physically attached pipettes are re-cached during robot.connect()
            # which is important, because during a simulation, the robot could
            # think that it holds a pipette model that it actually does not
            if not ff.use_protocol_api_v2():
                self._hardware.connect()
            unsubscribe()

            instruments, containers, modules, interactions = _accumulate(
                [_get_labware(command) for command in commands])

            self._containers.extend(_dedupe(containers))
            self._instruments.extend(_dedupe(instruments))
            self._modules.extend(_dedupe(modules))
            self._interactions.extend(_dedupe(interactions))

            # Labware calibration happens after simulation and before run, so
            # we have to clear the tips if they are left on after simulation
            # to ensure that the instruments are in the expected state at the
            # beginning of the labware calibration flow
            if not ff.use_protocol_api_v2():
                self._hardware.clear_tips()

        return res
Example #23
0
def test_execute_ok(protocol, protocol_file, ensure_api2, loop):
    proto = parse(protocol.text, protocol.filename)
    ctx = ProtocolContext(loop)
    execute.run_protocol(proto, context=ctx)
Example #24
0
    def _simulate(self):
        self._reset()

        stack = []
        res = []
        commands = []

        self._containers.clear()
        self._instruments.clear()
        self._modules.clear()
        self._interactions.clear()

        def on_command(message):
            payload = message['payload']
            description = payload.get('text', '').format(**payload)

            if message['$'] == 'before':
                level = len(stack)

                stack.append(message)
                commands.append(payload)

                res.append({
                    'level': level,
                    'description': description,
                    'id': len(res)
                })
            else:
                stack.pop()

        unsubscribe = self._broker.subscribe(command_types.COMMAND, on_command)
        old_robot_connect = robot.connect

        try:
            # ensure actual pipettes are cached before driver is disconnected
            self._hardware.cache_instruments()
            if self._use_v2:
                instrs = {}
                for mount, pip in self._hardware.attached_instruments.items():
                    if pip:
                        instrs[mount] = {
                            'model': pip['model'],
                            'id': pip.get('pipette_id', '')
                        }
                sim = adapters.SynchronousAdapter.build(
                    API.build_hardware_simulator,
                    instrs,
                    [mod.name() for mod in self._hardware.attached_modules],
                    strict_attached_instruments=False)
                sim.home()
                self._simulating_ctx = ProtocolContext(
                    loop=self._loop,
                    hardware=sim,
                    broker=self._broker,
                    bundled_labware=getattr(self._protocol, 'bundled_labware',
                                            None),
                    bundled_data=getattr(self._protocol, 'bundled_data', None),
                    extra_labware=getattr(self._protocol, 'extra_labware', {}))
                run_protocol(self._protocol, context=self._simulating_ctx)
            else:
                robot.broker = self._broker
                # we don't rely on being connected anymore so make sure we are
                robot.connect()
                robot.cache_instrument_models()
                robot.disconnect()

                def robot_connect_error(port=None, options=None):
                    raise RuntimeError(
                        'Protocols executed through the Opentrons App may not '
                        'use robot.connect(). Allowing this call would cause '
                        'the robot to execute commands during simulation, and '
                        'then raise an error on execution.')

                robot.connect = robot_connect_error
                exec(self._protocol.contents, {})
        finally:
            # physically attached pipettes are re-cached during robot.connect()
            # which is important, because during a simulation, the robot could
            # think that it holds a pipette model that it actually does not
            if not self._use_v2:
                robot.connect = old_robot_connect
                robot.connect()

            unsubscribe()

            instruments, containers, modules, interactions = _accumulate(
                [_get_labware(command) for command in commands])

            self._containers.extend(_dedupe(containers))
            self._instruments.extend(
                _dedupe(
                    instruments +
                    list(self._simulating_ctx.loaded_instruments.values())))
            self._modules.extend(
                _dedupe(modules + [
                    m._geometry
                    for m in self._simulating_ctx.loaded_modules.values()
                ]))
            self._interactions.extend(_dedupe(interactions))

            # Labware calibration happens after simulation and before run, so
            # we have to clear the tips if they are left on after simulation
            # to ensure that the instruments are in the expected state at the
            # beginning of the labware calibration flow
            if not self._use_v2:
                robot.clear_tips()

        return res