|
| 1 | +"""Mix Protocol with Error Recovery Turned Off.""" |
| 2 | +from opentrons.protocol_api import ( |
| 3 | + ProtocolContext, |
| 4 | + ParameterContext, |
| 5 | + Well, |
| 6 | + InstrumentContext, |
| 7 | +) |
| 8 | +from opentrons.hardware_control.types import Axis |
| 9 | + |
| 10 | +metadata = { |
| 11 | + "protocolName": "Mix without Error Recovery", |
| 12 | + "author": "Rhyann Clarke <[email protected]>", |
| 13 | +} |
| 14 | +requirements = { |
| 15 | + "robotType": "Flex", |
| 16 | + "apiLevel": "2.24", |
| 17 | +} |
| 18 | + |
| 19 | + |
| 20 | +def add_parameters(parameters: ParameterContext) -> None: |
| 21 | + """Add parameters for the Mix protocol.""" |
| 22 | + parameters.add_str( |
| 23 | + variable_name="left_mount", |
| 24 | + display_name="Left Mount", |
| 25 | + description="Pipette Type on Left Mount.", |
| 26 | + choices=[ |
| 27 | + {"display_name": "8ch 50ul", "value": "flex_8channel_50"}, |
| 28 | + {"display_name": "8ch 1000ul", "value": "flex_8channel_1000"}, |
| 29 | + {"display_name": "1ch 50ul", "value": "flex_1channel_50"}, |
| 30 | + {"display_name": "1ch 1000ul", "value": "flex_1channel_1000"}, |
| 31 | + {"display_name": "96ch 1000ul", "value": "flex_96channel_1000"}, |
| 32 | + {"display_name": "None", "value": "none"}, |
| 33 | + ], |
| 34 | + default="flex_8channel_1000", |
| 35 | + ) |
| 36 | + parameters.add_str( |
| 37 | + variable_name="tip_type", |
| 38 | + display_name="Tip Type", |
| 39 | + description="Tip Type to use for the test.", |
| 40 | + choices=[ |
| 41 | + {"display_name": "50 uL", "value": "opentrons_flex_96_tiprack_50ul"}, |
| 42 | + { |
| 43 | + "display_name": "50 uL Filter", |
| 44 | + "value": "opentrons_flex_96_filtertiprack_50ul", |
| 45 | + }, |
| 46 | + { |
| 47 | + "display_name": "200 µL", |
| 48 | + "value": "opentrons_flex_96_tiprack_200ul", |
| 49 | + }, |
| 50 | + {"display_name": "1000 µL", "value": "opentrons_flex_96_tiprack_1000ul"}, |
| 51 | + { |
| 52 | + "display_name": "1000 µL FILTER", |
| 53 | + "value": "opentrons_flex_96_filtertiprack_1000ul", |
| 54 | + }, |
| 55 | + ], |
| 56 | + default="opentrons_flex_96_tiprack_200ul", |
| 57 | + ) |
| 58 | + parameters.add_str( |
| 59 | + variable_name="tip_rack_slot", |
| 60 | + display_name="Tip Rack Slot", |
| 61 | + description="Slot for the tip rack.", |
| 62 | + choices=[ |
| 63 | + {"display_name": "A1", "value": "A1"}, |
| 64 | + {"display_name": "B1", "value": "B1"}, |
| 65 | + {"display_name": "C1", "value": "C1"}, |
| 66 | + {"display_name": "D1", "value": "D1"}, |
| 67 | + {"display_name": "A3", "value": "A3"}, |
| 68 | + {"display_name": "B3", "value": "B3"}, |
| 69 | + {"display_name": "C3", "value": "C3"}, |
| 70 | + {"display_name": "D3", "value": "D3"}, |
| 71 | + ], |
| 72 | + default="D1", |
| 73 | + ) |
| 74 | + parameters.add_float( |
| 75 | + variable_name="mix_volume", |
| 76 | + display_name="Mix Volume (ul)", |
| 77 | + maximum=1000, |
| 78 | + minimum=1, |
| 79 | + default=200, |
| 80 | + ) |
| 81 | + parameters.add_int( |
| 82 | + variable_name="mix_reps", |
| 83 | + display_name="Mix Repititions", |
| 84 | + maximum=10000, |
| 85 | + minimum=1, |
| 86 | + default=200, |
| 87 | + ) |
| 88 | + parameters.add_float( |
| 89 | + variable_name="flow_rate", |
| 90 | + display_name="Flow Rate", |
| 91 | + description="Mix flow rate.", |
| 92 | + maximum=850, |
| 93 | + minimum=1, |
| 94 | + default=716, |
| 95 | + ) |
| 96 | + |
| 97 | + |
| 98 | +def home_axes_only(ctx: ProtocolContext) -> None: |
| 99 | + """Home axes and no plungers.""" |
| 100 | + hw_api = ctx._core.get_hardware() |
| 101 | + hw_api.home([Axis.Z_L, Axis.Z_R, Axis.X, Axis.Y]) |
| 102 | + |
| 103 | + |
| 104 | +def safe_mix( |
| 105 | + pipette: InstrumentContext, |
| 106 | + location: Well, |
| 107 | + ctx: ProtocolContext, |
| 108 | +) -> None: |
| 109 | + """Perform mix manually, catching stalls.""" |
| 110 | + stall_count = 0 |
| 111 | + mix_volume = ctx.params.mix_volume # type: ignore[attr-defined] |
| 112 | + mix_reps = ctx.params.mix_reps # type: ignore[attr-defined] |
| 113 | + mix_flow_rate = ctx.params.flow_rate # type: ignore[attr-defined] |
| 114 | + |
| 115 | + for i in range(mix_reps): |
| 116 | + try: |
| 117 | + pipette.aspirate(mix_volume, location.bottom(z=1), flow_rate=mix_flow_rate) |
| 118 | + pipette.dispense(mix_volume, location.bottom(z=2), flow_rate=mix_flow_rate) |
| 119 | + except Exception as e: |
| 120 | + msg = str(e) |
| 121 | + if "stall" in msg: |
| 122 | + ctx.comment(f"Caught stall error (code 2003): {msg}") |
| 123 | + try: |
| 124 | + pipette._retract() # homes the z axis |
| 125 | + except Exception as e: |
| 126 | + msg = str(e) |
| 127 | + ctx.comment(f"second error {msg}") |
| 128 | + if "position" in msg: |
| 129 | + # if PositionUnknownError Occurs after the Z axis homes, t |
| 130 | + # he pipette will home all axes excluding the plunger. |
| 131 | + home_axes_only(ctx) |
| 132 | + pipette.blow_out(location.top()) |
| 133 | + pipette.home() # pipette homes again after blow out to ensure plunger is homed. |
| 134 | + pipette.move_to(location.top()) |
| 135 | + else: |
| 136 | + ctx.comment(f"Unhandled error: {msg}") |
| 137 | + raise |
| 138 | + stall_count += 1 |
| 139 | + continue |
| 140 | + ctx.comment(f"Total stalls: {stall_count}") |
| 141 | + |
| 142 | + |
| 143 | +def run(ctx: ProtocolContext) -> None: |
| 144 | + """Run the Mix protocol.""" |
| 145 | + # Load pipette |
| 146 | + left_mount = ctx.params.left_mount # type: ignore[attr-defined] |
| 147 | + tip_rack = ctx.load_labware( |
| 148 | + ctx.params.tip_type, ctx.params.tip_rack_slot # type: ignore[attr-defined] |
| 149 | + ) |
| 150 | + pipette = ctx.load_instrument( |
| 151 | + left_mount, |
| 152 | + "left", |
| 153 | + tip_racks=[tip_rack], |
| 154 | + ) |
| 155 | + reservoir = ctx.load_labware("opentrons_tough_12_reservoir_22ml", "D2") |
| 156 | + pipette.pick_up_tip() |
| 157 | + safe_mix(pipette, reservoir["A1"], ctx) |
0 commit comments