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 #3
0
def run(protocol: protocol_api.ProtocolContext):
    """A function to try out the home commands."""
    #load labware
    plate = protocol.load_labware('biorad_96_wellplate_200ul_pcr', 1,
                                  '96plate')
    #load pipette
    p300 = protocol.load_instrument('p300_single_gen2', 'right')

    p300.move_to(plate['A1'].top())  #move to top of well A1 of 96plate
    protocol.home()  #homes gantry, z axes, and plungers
    p300.move_to(plate['B1'].top())  #move to top of well B1 of 96plate
Example #4
0
def run(protocol: protocol_api.ProtocolContext):

    # define deck positions and labware

    # tips
    tiprack_200f = protocol.load_labware('opentrons_96_filtertiprack_200ul', 6)

    # tube racks, named for deck position
    tuberack_4 = protocol.load_labware(tuberack_labware, 4)
    tuberack_1 = protocol.load_labware(tuberack_labware, 1)
    tuberack_5 = protocol.load_labware(tuberack_labware, 5)
    tuberack_2 = protocol.load_labware(tuberack_labware, 2)

    # plates
    samples = protocol.load_labware('vwr_96_wellplate_1000ul', 3, 'samples')

    # initialize pipettes
    pipette_right = protocol.load_instrument('p300_single_gen2',
                                             'right',
                                             tip_racks=[tiprack_200f])

    # # home instrument
    protocol.home()

    # distribute quantity
    for i, rack in enumerate([tuberack_4, tuberack_5, tuberack_1, tuberack_2]):

        # Indices to locate source racks
        # X and Y are indices of tube rack positions
        # (0 left/top, 1 right/bottom)
        x = int(i % 2)
        y = int(floor(i / 2.0))

        # Indices to locate destination wells
        # a and b are from:to columns of the destination plate
        a = (x * 6)
        b = ((x + 1) * 6)
        # i and j are the from:to rows of the destination plate
        i = (y * 4)
        j = ((y + 1) * 4)

        # In this arrangement, the source tube racks are combined into four
        # quadrants of the destination plate, for a 1:1 spatial relationship
        # between the tubes on the deck and the wells in the destination plate.

        d_rows = [c[i:j] for c in samples.columns()]
        d_wells = d_rows[a:b]

        pipette_right.transfer(quantity,
                               [w.bottom(z=z_height) for w in rack.wells()],
                               d_wells,
                               new_tip='always',
                               rate=rate,
                               trash=True)
Example #5
0
def run(protocol: protocol_api.ProtocolContext):
    
    # define deck positions and labware
    
    # tips
    tiprack_200f = protocol.load_labware('opentrons_96_filtertiprack_200ul', 6)
    
    # tubes
    tuberack_4 = protocol.load_labware('opentrons_24_tuberack_generic_2ml_screwcap', 4)
    tuberack_1 = protocol.load_labware('opentrons_24_tuberack_generic_2ml_screwcap', 1)
    tuberack_5 = protocol.load_labware('opentrons_24_tuberack_generic_2ml_screwcap', 5)
    tuberack_2 = protocol.load_labware('opentrons_24_tuberack_generic_2ml_screwcap', 2)

    # plates
    samples = protocol.load_labware('biorad_96_wellplate_200ul_pcr', 3, 'samples')
    
    # initialize pipettes
    pipette_right = protocol.load_instrument('p300_single_gen2', 
                                            'right',
                                            tip_racks=[tiprack_200f])

    # # home instrument
    protocol.home()
    
    # distribute quantity 
    for i, rack in enumerate([tuberack_4,
                              tuberack_5,
                              tuberack_1,
                              tuberack_2]):

        x = int(i % 2)
        y = int(floor(i/2.0))

        # print('x: %s  y: %s' % (x,y))
        
        a = (x*6)
        b = ((x+1)*6)
        i = (y*4)
        j = ((y+1)*4)
        
        # print('a: %s  b: %s' % (a, b))
        # print('i: %s  j: %s' % (i, j))
        
        d_rows = [c[i:j] for c in samples.columns()]
        # print(d_rows)
        d_wells = d_rows[a:b]
        # print(d_wells)
        
        pipette_right.transfer(quantity,
                               [w.bottom(z=z_height) for w in rack.wells()],
                               d_wells,
                               new_tip='always',
                               trash=True)
Example #6
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()
def run(protocol: protocol_api.ProtocolContext):

    # ### HackFlex Illumina-compatible library prep protocol

    # ### Deck

    # 1. samples; libraries
    # 2. reagent reservoir
    # 3. reagent strip tubes
    # 4. 300 tips (wash); 200f tips (elute)
    # 5. 10f tips (samples)
    # 6. i7 primers
    # 7. waste
    # 8. 300 tips (reagents)
    # 9. 10f tips (primers)
    # 10. mag module
    # 11. 20 tips (reagents)
    # 12. trash

    # define custom labware for strip tubes block
    # reagent strip tubes:
    # 1: BLT 150 µL
    # 2: TSB 150 µL
    # 3: i5 primers 150 µL
    # 4: PCR MM 200 µL
    # 5: PCR MM 200 µL

    # buffer reservoirs:
    # 1: TB1 (2.5 mL)
    # 2: TWB (10 mL)
    # 3: TWB (10 mL)
    # 4: H2O (8 mL)
    # 5: beads (6 mL)
    # 6: 80% EtOH
    # 7: 80% EtOH
    # 8: 80% EtOH

    # ### Setup

    protocol.home()


    # define deck positions and labware

    # define hardware modules
    magblock = protocol.load_module('Magnetic Module', 10)
    magblock.disengage()

    # tips
    tiprack_samples = protocol.load_labware('opentrons_96_filtertiprack_10ul', 
                                            5)
    tiprack_buffers = protocol.load_labware('opentrons_96_tiprack_300ul', 
                                            8)
    tiprack_wash = protocol.load_labware('opentrons_96_tiprack_300ul', 
                                         4)
    tiprack_primers = protocol.load_labware('opentrons_96_filtertiprack_10ul', 
                                            9)
    tiprack_reagents = protocol.load_labware('opentrons_96_tiprack_20ul', 
                                             11)

    # reagents
    # should be new custom labware with strip tubes
    reagents = protocol.load_labware('opentrons_96_aluminumblock_generic_pcr_strip_200ul',
                                     3, 'reagents')
    buffers = protocol.load_labware('nest_12_reservoir_15ml', 
                                    2, 'wash buffers')
    waste = protocol.load_labware('nest_1_reservoir_195ml',
                                  7, 'liquid waste')

    # plates
    samples = protocol.load_labware('biorad_96_wellplate_200ul_pcr',
                                   1, 'samples')
    i7_primers = protocol.load_labware('biorad_96_wellplate_200ul_pcr',
                                       6, 'i7 primers')

    # load plate on magdeck
    # mag_plate = magblock.load_labware('vwr_96_wellplate_1000ul')
    mag_plate = magblock.load_labware('biorad_96_wellplate_200ul_pcr')

    # initialize pipettes
    pipette_left = protocol.load_instrument('p300_multi', 
                                            'left',
                                            tip_racks=[tiprack_buffers])
    pipette_right = protocol.load_instrument('p10_multi', 
                                            'right',
                                            tip_racks=[tiprack_reagents])

    # TWB wash wells
    twb_wells = [buffers[x] for x in twb_cols]

    # PCR MM wells
    pcr_wells = [reagents[x] for x in pcr_cols]

    # EtOH wells
    eth_wells = [buffers[x] for x in eth_cols]


    # DNA plate

    # Step 1: Tagmentation
    # Diluted BLT: 1 mL; 120 (150 µL) per tip
    # TB1: 2.4 mL; 300 (350 µL) per tip

    # add TB1.
    # buffer tips 1
    pipette_left.distribute(25,
                            buffers['A1'],
                            [mag_plate[x] for x in cols],
                            touch_tip=False,
                            disposal_volume=10,
                            new_tip='once',
                            trash=True)
    # add BLT
    # reagent tips 2

    # mix BLT first
    pipette_right.pick_up_tip()
    pipette_right.mix(10,
                      10,
                      reagents['A1'])
    pipette_right.transfer(10,
                           reagents['A1'],
                           [mag_plate[x] for x in cols],
                           mix_before=(2,10),
                           new_tip='never')
    pipette_right.drop_tip()

    # add sample

    for col in cols:
        pipette_right.pick_up_tip(tiprack_samples[col])
        pipette_right.transfer(10,
                               samples[col],
                               mag_plate[col],
                               mix_after=(5, 10),
                               new_tip='never',
                               trash=False)
        pipette_right.return_tip()

    # Prompt user to remove plate and run on thermocycler
    protocol.pause('Remove plate from magblock, seal, vortex, and run '
                   'program TAG on thermocycler. Then spin down, unseal, '
                   'and return to magblock.')


    # Step 2: Stop reaction
    # TSB: 1 mL; 120 (150 µL) per tip

    # add TSB to each sample.
    # Prompt user to remove plate and run on thermocycler

    ### Is this step going to cross-contaminate? Seems wasteful to take.
    ### new tip for each sample. z = -1 meant to help.  

    # reagent tips 2
    pipette_right.transfer(10,
                           reagents['A2'],
                           [mag_plate[x].top(z=-1) for x in cols],
                           touch_tip=True,
                           new_tip='once')

    protocol.pause('Remove plate from magblock, seal, vortex, and run '
                   'program PTC on thermocycler. Then spin down, unseal, '
                   'and return to magblock.')


    # Step 3: Cleanup
    # TWB: 20 mL; 1200 (1500 µL) per tip

    # Magnet wash 2X

    # bind for specified length of time

    protocol.comment('Binding beads to magnet.')
    magblock.engage(height_from_base=mag_engage_height)

    protocol.delay(seconds=pause_mag)


    # ### Do first wash: 100 µL TWB
    # buffer tips 2
    protocol.comment('Doing wash #1.')
    twb_remaining, twb_wells = bead_wash(# global arguments
                                         protocol,
                                         magblock,
                                         pipette_left,
                                         mag_plate,
                                         cols,
                                         # super arguments
                                         waste['A1'],
                                         tiprack_wash,
                                         # wash buffer arguments,
                                         twb_wells,
                                         10000/8,
                                         # mix arguments
                                         tiprack_wash,
                                         # optional arguments
                                         wash_vol=100,
                                         super_vol=60,
                                         drop_super_tip=False,
                                         mix_n=wash_mix,
                                         mix_vol=90,
                                         remaining=None)



    # ### Do second wash: 100 µL TWB
    # buffer tips 3
    protocol.comment('Doing wash #2.')
    twb_remaining, twb_wells = bead_wash(# global arguments
                                         protocol,
                                         magblock,
                                         pipette_left,
                                         mag_plate,
                                         cols,
                                         # super arguments
                                         waste['A1'],
                                         tiprack_wash,
                                         # wash buffer arguments,
                                         twb_wells,
                                         10000/8,
                                         # mix arguments
                                         tiprack_wash,
                                         # optional arguments
                                         wash_vol=100,
                                         super_vol=100,
                                         drop_super_tip=False,
                                         mix_n=wash_mix,
                                         mix_vol=90,
                                         remaining=twb_remaining)

    # remove supernatant
    remove_supernatant(pipette_left,
                       mag_plate,
                       cols,
                       tiprack_wash,
                       waste['A1'],
                       super_vol=120,
                       rate=bead_flow,
                       bottom_offset=.5,
                       drop_tip=False)

    magblock.disengage()

    # Step 3: amplification
    # MM: 3 mL; 350 (400 µL) per tip
    # buffer tips 4

    pcr_wells, pcr_remaining = add_buffer(pipette_left,
                                          mag_plate,
                                          cols,
                                          30,
                                          pcr_wells,
                                          200,
                                          tip=None,
                                          tip_vol=300,
                                          remaining=None,
                                          drop_tip=True)


    # plate: primers i5
    # reagent tips 3
    pipette_right.transfer(10,
                           reagents['A5'],
                           [mag_plate[x].top(z=-1) for x in cols],
                           touch_tip=True,
                           new_tip='once')


    # plate: primers i7
    for col in cols:
        pipette_right.pick_up_tip(tiprack_primers[col])
        pipette_right.transfer(10,
                               i7_primers[col],
                               mag_plate[col],
                               mix_after=(5, 10),
                               touch_tip=True,
                               new_tip='never',
                               trash=False)
        pipette_right.drop_tip()

    # Prompt user to remove plate and run on thermocycler

    protocol.pause('Remove plate from magblock, seal, and run amplification'
                   ' program on thermocycler.')



    # Step 4: Size selection
    # H2O: 72 µL per sample; 7.2 mL; 600 per tip
    # EtOH: 400 µL per sample; 5000 per tip
    # Beads: 6 mL; 720 µL per tip

    # 

    protocol.pause('Remove sample plate from position {0}, seal, and store. '
                   'Place a new, clean, 96-well BioRad PCR plate in position'
                   ' {0}.'.format(samples.parent))

    protocol.pause('Centrifuge sealed plate at 280 xg for one minute.'
                   ' Then unseal and return to magblock.')

    protocol.comment('Binding beads to magnet.')
    magblock.engage(height_from_base=mag_engage_height)


    # Add buffers for large-cut size selection to new plate
    protocol.comment('Preparing large-cut bead conditions in new plate.')

    # add 40 µL H2O
    # buffer tips 5
    pipette_left.distribute(40,
                            buffers['A4'],
                            [samples[x] for x in cols],
                            touch_tip=True,
                            disposal_volume=10,
                            new_tip='once') 

    # Add 45 µL SPRI beads
    # buffer tips 6
    pipette_left.pick_up_tip()
    pipette_left.mix(10, 200, buffers['A5'])
    pipette_left.distribute(45,
                            buffers['A5'],
                            [samples[x] for x in cols],
                            mix_before=(2,40),
                            touch_tip=True,
                            disposal_volume=10,
                            new_tip='never')
    pipette_left.drop_tip() 

    # Transfer 45 µL PCR supernatant to new plate
    for col in cols:
        pipette_left.pick_up_tip(tiprack_wash.wells_by_name()[col])
        pipette_left.transfer(45,
                             [mag_plate[x] for x in cols],
                             [samples[x] for x in cols],
                             mix_after=(10, 100),
                             touch_tip=True,
                             new_tip='never',
                             trash=False)
        pipette_left.return_tip()

    protocol.pause('Remove and discard plate from mag block. '
                   'Move plate in position {0} to mag block, and replace '
                   'with a new, clean 96-well BioRad PCR plate.'.format(
                    samples.parent))

    protocol.comment('Binding beads to magnet.')
    magblock.engage(height_from_base=mag_engage_height)
    protocol.delay(seconds=pause_mag)


    # Add buffers for small-cut size selection to new plate
    # Add 15 µL SPRI beads
    # buffer tips 7
    pipette_left.pick_up_tip()
    pipette_left.mix(10, 100, buffers['A5'])
    pipette_left.distribute(15,
                            buffers['A5'],
                            [samples[x] for x in cols],
                            mix_before=(2,15),
                            touch_tip=True,
                            new_tip='never') 
    pipette_left.drop_tip()


    # Transfer 125 µL large-cut supernatant to new plate
    for col in cols:
        pipette_left.pick_up_tip(tiprack_wash.wells_by_name()[col])
        pipette_left.transfer(125,
                             [mag_plate[x] for x in cols],
                             [samples[x] for x in cols],
                             mix_after=(10, 100),
                             touch_tip=True,
                             new_tip='never',
                             trash=False)
        pipette_left.return_tip()

    protocol.pause('Remove and discard plate from mag block. '
                   'Move plate in position {0} to mag block, and replace '
                   'with a new, clean 96-well BioRad PCR plate.'.format(
                    samples.parent))

    protocol.comment('Binding beads to magnet.')
    magblock.engage(height_from_base=mag_engage_height)
    protocol.delay(seconds=pause_mag)

    # ### Do first wash: 150 µL EtOH
    # buffer tips 8
    protocol.comment('Doing wash #1.')
    eth_remaining, eth_wells = bead_wash(# global arguments
                                         protocol,
                                         magblock,
                                         pipette_left,
                                         mag_plate,
                                         cols,
                                         # super arguments
                                         waste['A1'],
                                         tiprack_wash,
                                         # wash buffer arguments,
                                         eth_wells,
                                         14000/8,
                                         # mix arguments
                                         tiprack_wash,
                                         # optional arguments
                                         wash_vol=150,
                                         super_vol=125,
                                         drop_super_tip=False,
                                         mix_n=wash_mix,
                                         mix_vol=140,
                                         remaining=None)


    # ### Do first wash: 150 µL EtOH
    # buffer tips 9
    protocol.comment('Doing wash #1.')
    eth_remaining, eth_wells = bead_wash(# global arguments
                                         protocol,
                                         magblock,
                                         pipette_left,
                                         mag_plate,
                                         cols,
                                         # super arguments
                                         waste['A1'],
                                         tiprack_wash,
                                         # wash buffer arguments,
                                         eth_wells,
                                         14000/8,
                                         # mix arguments
                                         tiprack_wash,
                                         # optional arguments
                                         wash_vol=150,
                                         super_vol=125,
                                         drop_super_tip=False,
                                         mix_n=wash_mix,
                                         mix_vol=140,
                                         remaining=eth_remaining)



    # ### Dry
    protocol.comment('Removing wash and drying beads.')

    # This should:
    # - pick up tip in position 8
    # - pick up supernatant from magplate
    # - dispense in waste, position 11
    # - repeat
    # - trash tip
    # - leave magnet engaged

    # remove supernatant

    remove_supernatant(pipette_left,
                       mag_plate,
                       cols,
                       tiprack_wash,
                       waste['A1'],
                       super_vol=170,
                       rate=bead_flow,
                       bottom_offset=.5,
                       drop_tip=True)

    # dry

    protocol.delay(seconds=pause_dry)


    protocol.pause('Replace empty tiprack in position {0} with new rack of '
                   '200 µL filter tips.'.format(tiprack_wash.parent))


    # ### Elute
    protocol.comment('Eluting DNA from beads.')

    # This should:
    # - disengage magnet
    # - pick up tip from position 6
    # - pick up reagents from column 2 of position 9
    # - dispense into magplate
    # - mix 10 times
    # - blow out, touch tip
    # - return tip to position 6
    # - wait (5 seconds)
    # - engage magnet
    # - wait (5 seconds)
    # - pick up tip from position 6
    # - aspirate from magplate
    # - dispense to position 3
    # - trash tip


    # transfer elution buffer to mag plate

    magblock.disengage()

    # add elution buffer and mix
    for col in cols:
        pipette_left.pick_up_tip(tiprack_wash.wells_by_name()[col])
        pipette_left.aspirate(32, buffers['A4'], rate=1)
        pipette_left.dispense(32, mag_plate[col].bottom(z=1))
        pipette_left.mix(10, 25, mag_plate[col].bottom(z=1))
        pipette_left.blow_out(mag_plate[col].top())
        pipette_left.touch_tip()
        # we'll use these same tips for final transfer
        pipette_left.return_tip()
    
    protocol.delay(seconds=pause_elute)
    for col in cols:
        pipette_left.pick_up_tip(tiprack_wash.wells_by_name()[col])
        pipette_left.mix(10, 25, mag_plate[col].bottom(z=1))
        pipette_left.blow_out(mag_plate[col].top())
        pipette_left.touch_tip()
        # we'll use these same tips for final transfer
        pipette_left.return_tip()

    # bind to magnet
    protocol.comment('Binding beads to magnet.')

    magblock.engage(height_from_base=mag_engage_height)

    protocol.delay(seconds=pause_mag)

    protocol.comment('Transferring eluted DNA to final plate.')
    for col in cols:
        pipette_left.pick_up_tip(tiprack_wash.wells_by_name()[col])
        pipette_left.aspirate(32, 
                              mag_plate[col].bottom(z=2),
                              rate=bead_flow)
        pipette_left.dispense(32, samples[col])
        pipette_left.blow_out(samples[col].top())
        pipette_left.touch_tip()
        # we're done with these tips now
        pipette_left.drop_tip()

    magblock.disengage()
Example #8
0
def run(ctx: protocol_api.ProtocolContext):
    w1_tip_pos_list             = []
    w2_tip_pos_list             = []
    elution_tip_pos_list        = []

    ctx.comment('Columnas a utilizar: '+str(num_cols))

    STEP = 0
    STEPS = { #Dictionary with STEP activation, description, and times
            1:{'Execute': True, 'description': 'Transferir bolas magnéticas'},
            2:{'Execute': True, 'description': 'Incubación con el imán ON', 'wait_time': 600}, 
            3:{'Execute': True, 'description': 'Desechar sobrenadante'},
            4:{'Execute': True, 'description': 'Imán OFF'},
            5:{'Execute': True, 'description': 'Transferir primer lavado'},
            6:{'Execute': True, 'description': 'Incubación con el imán ON', 'wait_time': 300},
            7:{'Execute': True, 'description': 'Desechar sobrenadante'},
            8:{'Execute': True, 'description': 'Imán OFF'},
            9:{'Execute': True, 'description': 'Transferir segundo lavado'},
            10:{'Execute': True, 'description': 'Incubación con el imán ON', 'wait_time': 300},
            11:{'Execute': True, 'description': 'Desechar sobrenadante'},
            12:{'Execute': True, 'description': 'Secado', 'wait_time': 180},
            13:{'Execute': True, 'description': 'Imán OFF'},
            14:{'Execute': True, 'description': 'Transferir elución'},
            15:{'Execute': True, 'description': 'Incubación con el imán ON', 'wait_time': 180},
            16:{'Execute': True, 'description': 'Transferir elución a la placa'},
            }

    #Folder and file_path for log time
    import os
    folder_path = '/var/lib/jupyter/notebooks' + run_id
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/Station_B_Extraccion_total_time_log.txt'

    #Define Reagents as objects with their properties
    class Reagent:
        def calc_vol_well(self):
            if(self.name == 'Sample'):
                self.num_wells = num_cols
                return VOLUME_SAMPLE
            else:    
                trips = math.ceil(self.reagent_volume / self.max_volume_allowed)
                vol_trip = self.reagent_volume / trips * 8
                max_trips_well = math.floor(11000 / vol_trip)
                total_trips = num_cols * trips
                self.num_wells = math.ceil(total_trips / max_trips_well)
                return math.ceil(total_trips / self.num_wells) * vol_trip + self.dead_vol

        def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, flow_rate_aspirate_mix, flow_rate_dispense_mix,
        air_gap_vol_bottom, air_gap_vol_top, disposal_volume, rinse, max_volume_allowed, reagent_volume, h_cono, v_fondo, tip_recycling = 'none', dead_vol = 700):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.flow_rate_aspirate_mix = flow_rate_aspirate_mix
            self.flow_rate_dispense_mix = flow_rate_dispense_mix
            self.air_gap_vol_bottom = air_gap_vol_bottom
            self.air_gap_vol_top = air_gap_vol_top
            self.disposal_volume = disposal_volume
            self.rinse = bool(rinse)
            self.max_volume_allowed = max_volume_allowed
            self.reagent_volume = reagent_volume
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.tip_recycling = tip_recycling
            self.dead_vol = dead_vol
            self.vol_well_original = self.calc_vol_well() if reagent_volume * NUM_SAMPLES > 0 else 0

    #Reagents and their characteristics
    Beads = Reagent(name = 'Beads',
                    flow_rate_aspirate = 25, #
                    flow_rate_dispense = 100, #
                    flow_rate_aspirate_mix = 25, #
                    flow_rate_dispense_mix = 100, #
                    air_gap_vol_bottom = 5,
                    air_gap_vol_top = 0,
                    disposal_volume = 1,
                    rinse = True,
                    max_volume_allowed = 180,
                    reagent_volume = BEADS_VOLUME_PER_SAMPLE, # reagent volume needed per sample
                    h_cono = 1.95,
                    v_fondo = 695, #1.95 * multi_well_rack_area / 2, #Prismatic
                    tip_recycling = 'A1')

    Wash_1 = Reagent(name = 'WASH_1',
                    flow_rate_aspirate = 25,
                    flow_rate_dispense = 100,
                    flow_rate_aspirate_mix = 25,
                    flow_rate_dispense_mix = 100,
                    air_gap_vol_bottom = 5,
                    air_gap_vol_top = 0,
                    disposal_volume = 1,
                    rinse = True,
                    max_volume_allowed = 180,
                    reagent_volume = WASH_1_VOLUME_PER_SAMPLE, 
                    h_cono = 1.95,
                    v_fondo = 695, #1.95 * multi_well_rack_area / 2, #Prismatic
                    tip_recycling = 'A1')

    Wash_2 = Reagent(name = 'WASH_2',
                    flow_rate_aspirate = 25,
                    flow_rate_dispense = 100,
                    flow_rate_aspirate_mix = 25,
                    flow_rate_dispense_mix = 100,
                    air_gap_vol_bottom = 5,
                    air_gap_vol_top = 0,
                    disposal_volume = 1,
                    rinse = True,
                    max_volume_allowed = 180,
                    reagent_volume = WASH_2_VOLUME_PER_SAMPLE, 
                    h_cono = 1.95,
                    v_fondo = 695, #1.95 * multi_well_rack_area / 2, #Prismatic
                    tip_recycling = 'A1')

    Elution = Reagent(name = 'Elution',
                    flow_rate_aspirate = 25,
                    flow_rate_dispense = 100,
                    flow_rate_aspirate_mix = 25,
                    flow_rate_dispense_mix = 100,
                    air_gap_vol_bottom = 5,
                    air_gap_vol_top = 0,
                    disposal_volume = 1,
                    rinse = False,
                    max_volume_allowed = 180,
                    reagent_volume = ELUTION_VOLUME_PER_SAMPLE,
                    h_cono = 1.95,
                    v_fondo = 695) #1.95*multi_well_rack_area/2) #Prismatic

    Sample = Reagent(name = 'Sample',
                    flow_rate_aspirate = 5, # Original 0.5
                    flow_rate_dispense = 100, # Original 1
                    flow_rate_aspirate_mix = 1,
                    flow_rate_dispense_mix = 1,
                    air_gap_vol_bottom = 5,
                    air_gap_vol_top = 0,
                    disposal_volume = 1,
                    rinse = False,
                    max_volume_allowed = 180,
                    reagent_volume = VOLUME_SAMPLE,
                    h_cono = 4,
                    v_fondo = 4 * math.pi * 4**3 / 3) #Sphere

    Beads.vol_well      = Beads.vol_well_original
    Wash_1.vol_well     = Wash_1.vol_well_original
    Wash_2.vol_well     = Wash_2.vol_well_original
    Elution.vol_well    = Elution.vol_well_original
    Sample.vol_well     = 350 # Arbitrary value

    #########
    def str_rounded(num):
        return str(int(num + 0.5))

    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('VALORES DE VARIABLES')
    ctx.comment(' ')
    ctx.comment('Número de muestras: ' + str(NUM_SAMPLES)) 
    ctx.comment('Volumen de muestra en el deepwell: ' + str(VOLUME_SAMPLE) + ' ul') 
    ctx.comment('Volumen de solución con bolas magnéticas por muestra: ' + str(BEADS_VOLUME_PER_SAMPLE) + ' ul')
    ctx.comment('Volumen del primer lavado por muestra: ' + str(WASH_1_VOLUME_PER_SAMPLE) + ' ul') 
    ctx.comment('Volumen del segundo lavado por muestra: ' + str(WASH_2_VOLUME_PER_SAMPLE) + ' ul') 
    ctx.comment('Volumen de elución por muestra: ' + str(ELUTION_VOLUME_PER_SAMPLE) + ' ul') 	
    ctx.comment('Volumen de elución a retirar del deepwell: ' + str(ELUTION_FINAL_VOLUME_PER_SAMPLE) + ' ul') 	 	
    ctx.comment('Número de mezclas en la primera recogida de un canal con bolas magnéticas: ' + str(BEADS_WELL_FIRST_TIME_NUM_MIXES))
    ctx.comment('Número de mezclas en el resto de recogidas de un canal con bolas magnéticas: ' + str(BEADS_WELL_NUM_MIXES)) 	
    ctx.comment('Número de mezclas con la solución de bolas magnéticas: ' + str(BEADS_NUM_MIXES))
    ctx.comment('Número de mezclas con el primer lavado: ' + str(WASH_1_NUM_MIXES)) 
    ctx.comment('Número de mezclas con el segundo lavado: ' + str(WASH_2_NUM_MIXES)) 
    ctx.comment('Número de mezclas con la elución: ' + str(ELUTION_NUM_MIXES)) 	
    ctx.comment('Reciclado de puntas en los lavados activado: ' + str(TIP_RECYCLING_IN_WASH)) 
    ctx.comment('Reciclado de puntas en la elución activado: ' + str(TIP_RECYCLING_IN_ELUTION))
    ctx.comment('Foto-sensible: ' + str(PHOTOSENSITIVE)) 	
    ctx.comment(' ')
    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('VOLÚMENES PARA ' + str(NUM_SAMPLES) + ' MUESTRAS')
    ctx.comment(' ')
    ctx.comment('Beads: ' + str(Beads.num_wells) + ' canales desde el canal 1 en el reservorio de 12 canales con un volumen de ' + str_rounded(Beads.vol_well_original) + ' uL cada uno')
    ctx.comment('Wash 1: ' + str(Wash_1.num_wells) + ' canales desde el canal 5 en el reservorio de 12 canales con un volumen de ' + str_rounded(Wash_1.vol_well_original) + ' uL cada uno')
    ctx.comment('Wash 2: ' + str(Wash_2.num_wells) + ' canales desde el canal 7 en el reservorio de 12 canales con un volumen de ' + str_rounded(Wash_2.vol_well_original) + ' uL cada uno')
    ctx.comment('Elution: ' + str(Elution.num_wells) + ' canales desde el canal 11 en el reservorio de 12 canales con un volumen de ' + str_rounded(Elution.vol_well_original) + ' uL cada uno')
    ctx.comment('###############################################')
    ctx.comment(' ')

    ###################
    #Custom functions
    def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, offset, wait_time = 0, drop_height = -1, two_thirds_mix_bottom = False):
        '''
        Function for mix in the same location a certain number of rounds. Blow out optional. Offset
        can set to 0 or a higher/lower value which indicates the lateral movement
        '''
        if mix_height <= 0:
            mix_height = 1
        pipet.aspirate(1, location = location.bottom(z = mix_height), rate = reagent.flow_rate_aspirate_mix)
        for i in range(rounds):
            pipet.aspirate(vol, location = location.bottom(z = mix_height), rate = reagent.flow_rate_aspirate_mix)
            if two_thirds_mix_bottom and i < ((rounds / 3) * 2):
                pipet.dispense(vol, location = location.bottom(z = 5).move(Point(x = offset)), rate = reagent.flow_rate_dispense_mix)
            else:
                pipet.dispense(vol, location = location.top(z = drop_height).move(Point(x = offset)), rate = reagent.flow_rate_dispense_mix)
        pipet.dispense(1, location = location.bottom(z = mix_height), rate = reagent.flow_rate_dispense_mix)
        if blow_out == True:
            pipet.blow_out(location.top(z = -2)) # Blow out
        if wait_time != 0:
            ctx.delay(seconds=wait_time, msg='Esperando durante ' + str(wait_time) + ' segundos.')

    def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.4):
        nonlocal ctx
        ctx.comment('Volumen útil restante ' + str(reagent.vol_well - reagent.dead_vol) +
                    ' < volumen necesario ' + str(aspirate_volume - Sample.disposal_volume * 8) + '?')
        if (reagent.vol_well - reagent.dead_vol + 1) < (aspirate_volume - Sample.disposal_volume * 8):
            ctx.comment('Se debe utilizar el siguiente canal')
            ctx.comment('Canal anterior: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('Nuevo canal: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('Nuevo volumen:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area
                    #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - (aspirate_volume - Sample.disposal_volume * 8)
            ctx.comment('Volumen restante:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area
            reagent.vol_well = reagent.vol_well - (aspirate_volume - (Sample.disposal_volume * 8))
            ctx.comment('La altura calculada es ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('La altura usada es ' + str(height))
            col_change = False
        return height, col_change

    def move_vol_multi(pipet, reagent, source, dest, vol, x_offset_source, x_offset_dest, pickup_height, rinse, 
        avoid_droplet, wait_time, blow_out, touch_tip = False, touch_tip_v_offset = 0, drop_height = -5, 
        aspirate_with_x_scroll = False, dispense_bottom_air_gap_before = False):
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet, reagent, location = source, vol = vol, rounds = 20, blow_out = False, mix_height = 3, offset = 0)

        # SOURCE
        if dispense_bottom_air_gap_before and reagent.air_gap_vol_bottom:
            pipet.dispense(reagent.air_gap_vol_bottom, source.top(z = -2), rate = reagent.flow_rate_dispense)

        if reagent.air_gap_vol_top != 0: #If there is air_gap_vol, switch pipette to slow speed
            pipet.move_to(source.top(z = 0))
            pipet.air_gap(reagent.air_gap_vol_top) #air gap

        if aspirate_with_x_scroll:
            aspirate_with_x_scrolling(pip = pipet, volume = vol, src = source, pickup_height = pickup_height, rate = reagent.flow_rate_aspirate, start_x_offset_src = 0, stop_x_offset_src = x_offset_source)
        else:    
            s = source.bottom(pickup_height).move(Point(x = x_offset_source))
            pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate) # aspirate liquid

        if reagent.air_gap_vol_bottom != 0: #If there is air_gap_vol, switch pipette to slow speed
            pipet.move_to(source.top(z = 0))
            pipet.air_gap(reagent.air_gap_vol_bottom) #air gap

        # if wait_time != 0:
        #     ctx.delay(seconds=wait_time, msg='Esperando durante ' + str(wait_time) + ' segundos.')

        if avoid_droplet == True: # Touch the liquid surface to avoid droplets
            ctx.comment("Moviendo a: " + str(pickup_height))
            pipet.move_to(source.bottom(pickup_height))

        # GO TO DESTINATION
        d = dest.top(z = drop_height).move(Point(x = x_offset_dest))
        pipet.dispense(vol - reagent.disposal_volume + reagent.air_gap_vol_bottom, d, rate = reagent.flow_rate_dispense)

        if reagent.air_gap_vol_top != 0:
            pipet.dispense(reagent.air_gap_vol_top, dest.top(z = 0), rate = reagent.flow_rate_dispense)

        if blow_out == True:
            pipet.blow_out(dest.top(z = drop_height))

        if touch_tip == True:
            pipet.touch_tip(speed = 20, v_offset = touch_tip_v_offset, radius=0.7)
            
        if wait_time != 0:
            ctx.delay(seconds=wait_time, msg='Esperando durante ' + str(wait_time) + ' segundos.')

        #if reagent.air_gap_vol_bottom != 0:
            #pipet.move_to(dest.top(z = 0))
            #pipet.air_gap(reagent.air_gap_vol_bottom) #air gap
            #pipet.aspirate(air_gap_vol_bottom, dest.top(z = 0),rate = reagent.flow_rate_aspirate) #air gap

    def aspirate_with_x_scrolling(pip, volume, src, pickup_height = 0, rate = 1, start_x_offset_src = 0, stop_x_offset_src = 0):

        max_asp = volume/pip.min_volume
        inc_step = (start_x_offset_src - stop_x_offset_src) / max_asp

        for x in reversed(np.arange(stop_x_offset_src, start_x_offset_src, inc_step)):
            s = src.bottom(pickup_height).move(Point(x = x))
            pip.aspirate(volume = pip.min_volume, location = s, rate = rate)

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip, position = None):
        nonlocal tip_track
        #if not ctx.is_simulating():
        if recycle_tip:
            pip.pick_up_tip(tips300[0].wells()[0])
        else:
            if tip_track['counts'][pip] >= tip_track['maxes'][pip]:
                for i in range(3):
                    ctx._hw_manager.hardware.set_lights(rails=False)
                    ctx._hw_manager.hardware.set_lights(button=(1, 0 ,0))
                    time.sleep(0.3)
                    ctx._hw_manager.hardware.set_lights(rails=True)
                    ctx._hw_manager.hardware.set_lights(button=(0, 0 ,1))
                    time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button=(0, 1 ,0))
                ctx.pause('Reemplaza las cajas de puntas de ' + str(pip.max_volume) + 'µl antes \
                de continuar.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0
                tip_track['num_refills'][pip] += 1
            if position is None:
                pip.pick_up_tip()
            else:
                pip.pick_up_tip(position)

    def start_run():
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Empezando protocolo')
        if PHOTOSENSITIVE == False:
            ctx._hw_manager.hardware.set_lights(button = True, rails =  True)
        else:
            ctx._hw_manager.hardware.set_lights(button = True, rails =  False)
        now = datetime.now()
        # dd/mm/YY H:M:S
        start_time = now.strftime("%Y/%m/%d %H:%M:%S")
        return start_time

    def finish_run():
        ctx.comment('###############################################')
        ctx.comment('Protocolo finalizado')
        ctx.comment(' ')
        #Set light color to blue
        ctx._hw_manager.hardware.set_lights(button = True, rails =  False)
        now = datetime.now()
        # dd/mm/YY H:M:S
        finish_time = now.strftime("%Y/%m/%d %H:%M:%S")
        if PHOTOSENSITIVE==False:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button = False, rails =  False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button = True, rails =  True)
                time.sleep(0.3)
        else:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button = False, rails =  False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button = True, rails =  False)
                time.sleep(0.3)
        ctx._hw_manager.hardware.set_lights(button = True, rails =  False)

        used_tips = tip_track['num_refills'][m300] * 96 * len(m300.tip_racks) + tip_track['counts'][m300]
        ctx.comment('Puntas de 200 ul utilizadas: ' + str(used_tips) + ' (' + str(round(used_tips / 96, 2)) + ' caja(s))')
        ctx.comment('###############################################')

        return finish_time

    def log_step_start():
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('PASO '+str(STEP)+': '+STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')
        return datetime.now()

    def log_step_end(start):
        end = datetime.now()
        time_taken = (end - start)
        STEPS[STEP]['Time:'] = str(time_taken)

        ctx.comment(' ')
        ctx.comment('Paso ' + str(STEP) + ': ' +STEPS[STEP]['description'] + ' hizo un tiempo de ' + str(time_taken))
        ctx.comment(' ')

    ##########
    def find_side(col):
        if col%2 == 0:
            side = -1 # left
        else:
            side = 1 # right
        return side


####################################
    # load labware and modules
    ######## 12 well rack
    reagent_res = ctx.load_labware('nest_12_reservoir_15ml', '5','reagent deepwell plate')

##################################
    ####### Elution plate - final plate, goes to C
    elution_plate = ctx.load_labware('rochemagnapure_96_wellplate_400ul', '1','ROCHE MagnaPure 96 Well Plate 400 uL')

############################################
    ######## Deepwell - comes from A
    magdeck = ctx.load_module('Magnetic Module Gen2', '4')
    deepwell_plate = magdeck.load_labware('nest_96_wellplate_2ml_deep', 'NEST 96 Deepwell Plate 2mL') # Change to NEST deepwell plate.
    
####################################
    ######## Waste reservoir
    waste_reservoir = ctx.load_labware('nest_1_reservoir_195ml', '7', 'waste reservoir') # Change to our waste reservoir
    waste = waste_reservoir.wells()[0] # referenced as reservoir

####################################
    ######### Load tip_racks
    tips300 = [ctx.load_labware('opentrons_96_tiprack_300ul', slot, '200µl filter tiprack')
        for slot in ['3', '6', '8', '9', '10', '11']]

###############################################################################
    #Declare which reagents are in each reservoir as well as deepwell and elution plate
    Beads.reagent_reservoir     = reagent_res.rows()[0][0:4]
    Wash_1.reagent_reservoir    = reagent_res.rows()[0][4:6]
    Wash_2.reagent_reservoir    = reagent_res.rows()[0][6:8]
    Elution.reagent_reservoir   = reagent_res.rows()[0][10:11]
    work_destinations           = deepwell_plate.rows()[0][:Sample.num_wells]
    final_destinations          = elution_plate.rows()[0][:Sample.num_wells]

    # pipettes.
    m300 = ctx.load_instrument('p300_multi_gen2', 'left', tip_racks = tips300) # Load multi pipette

    #### used tip counter and set maximum tips available
    tip_track = {
        'counts': {m300: 0},
        'maxes': {m300: 96 * len(m300.tip_racks)}, #96 tips per tiprack * number or tipracks in the layout
        'num_refills' : {m300 : 0},
        'tips': { m300: [tip for rack in tips300 for tip in rack.rows()[0]]}
        }

###############################################################################
    start_run()
    magdeck.disengage()

    ###############################################################################    
    # STEP 1 Transferir bolas magnéticas
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
    #Transferir bolas magnéticas
        start = log_step_start()

        beads_trips = math.ceil(Beads.reagent_volume / Beads.max_volume_allowed)
        beads_volume = Beads.reagent_volume / beads_trips
        beads_transfer_vol = []
        for i in range(beads_trips):
            beads_transfer_vol.append(beads_volume + Beads.disposal_volume)
        x_offset_source = 0
        x_offset_dest   = 0
        rinse = False # Original: True 
        first_mix_done = False

        for i in range(num_cols):
            ctx.comment("Column: " + str(i))
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j,transfer_vol in enumerate(beads_transfer_vol):
                #Calculate pickup_height based on remaining volume and shape of container
                # transfer_vol_extra = transfer_vol if j > 0 else transfer_vol + 100  # Extra 100 isopropanol for calcs
                # [pickup_height, change_col] = calc_height(Beads, multi_well_rack_area, transfer_vol_extra * 8)    
                [pickup_height, change_col] = calc_height(Beads, multi_well_rack_area, transfer_vol * 8)    
                if change_col == True or not first_mix_done: #If we switch column because there is not enough volume left in current reservoir column we mix new column
                    ctx.comment('Mezclando nuevo canal del reservorio: ' + str(Beads.col + 1))
                    custom_mix(m300, Beads, Beads.reagent_reservoir[Beads.col],
                        vol = Beads.max_volume_allowed, rounds = BEADS_WELL_FIRST_TIME_NUM_MIXES, 
                        blow_out = False, mix_height = 1.5, offset = 0)
                    first_mix_done = True
                else:
                    ctx.comment('Mezclando canal del reservorio: ' + str(Beads.col + 1))
                    mix_height = 1.5 if pickup_height > 1.5 else pickup_height
                    custom_mix(m300, Beads, Beads.reagent_reservoir[Beads.col],
                        vol = Beads.max_volume_allowed, rounds = BEADS_WELL_NUM_MIXES, 
                        blow_out = False, mix_height = mix_height, offset = 0)

                ctx.comment('Aspirando desde la columna del reservorio: ' + str(Beads.col + 1))
                ctx.comment('La altura de recogida es ' + str(pickup_height))
                move_vol_multi(m300, reagent = Beads, source = Beads.reagent_reservoir[Beads.col],
                        dest = work_destinations[i], vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                        pickup_height = pickup_height, rinse = rinse, avoid_droplet = False, wait_time = 0, blow_out = True, touch_tip = False, drop_height = 1)
            
            if BEADS_NUM_MIXES > 0:
                ctx.comment(' ')
                ctx.comment('Mezclando muestra ')
                custom_mix(m300, Beads, location = work_destinations[i], vol =  Beads.max_volume_allowed,
                        rounds = BEADS_NUM_MIXES, blow_out = False, mix_height = 1, offset = 0, wait_time = 2)
            
            m300.move_to(work_destinations[i].top(0))
            m300.air_gap(Beads.air_gap_vol_bottom) #air gap

            if recycle_tip:
                m300.return_tip()
            else:
                m300.drop_tip(home_after = False)
            tip_track['counts'][m300] += 8        
            
        log_step_end(start)
        ###############################################################################
        # STEP 1 Transferir bolas magnéticas
        ########
    
    ###############################################################################
    # STEP 2 Incubación con el imán ON
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        ctx.comment(' ')
        magdeck.engage(height = mag_height)
        ctx.delay(seconds = STEPS[STEP]['wait_time'], msg = 'Incubación con el imán ON durante ' + format(STEPS[STEP]['wait_time']) + ' segundos.')
        ctx.comment(' ')

        log_step_end(start)
        ###############################################################################
        # STEP 2 Incubación con el imán ON
        ########

    ###############################################################################
    # STEP 3 Desechar sobrenadante
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        supernatant_trips = math.ceil((Sample.reagent_volume + Beads.reagent_volume) / Sample.max_volume_allowed)
        supernatant_volume = Sample.max_volume_allowed # We try to remove an exceeding amount of supernatant to make sure it is empty
        supernatant_transfer_vol = []
        for i in range(supernatant_trips):
            supernatant_transfer_vol.append(supernatant_volume + Sample.disposal_volume)
        
        x_offset_rs = 2
        pickup_height = 0.5 # Original 0.5

        for i in range(num_cols):
            x_offset_source = find_side(i) * x_offset_rs
            x_offset_dest   = 0
            not_first_transfer = False

            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for transfer_vol in supernatant_transfer_vol:
                ctx.comment('Aspirando de la columna del deepwell: ' + str(i+1))
                ctx.comment('La altura de recogida es ' + str(pickup_height) )

                move_vol_multi(m300, reagent = Sample, source = work_destinations[i],
                        dest = waste, vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                        pickup_height = pickup_height, rinse = False, avoid_droplet = False, wait_time = 2, blow_out = True,
                        dispense_bottom_air_gap_before = not_first_transfer, drop_height = waste_drop_height)
                m300.move_to(waste.top(z = waste_drop_height))
                m300.air_gap(Sample.air_gap_vol_bottom)
                not_first_transfer = True

            if recycle_tip:
                m300.return_tip()
            else:
                m300.drop_tip(home_after = False)
            tip_track['counts'][m300] += 8

        log_step_end(start)
        ###############################################################################
        # STEP 3 Desechar sobrenadante
        ########

    ###############################################################################
    # STEP 4 MAGNET OFF
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        # Imán OFF
        magdeck.disengage()

        log_step_end(start)
        ###############################################################################
        # STEP 4 MAGNET OFF
        ########

    ###############################################################################
    # STEP 5 Transferir primer lavado
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        wash_trips = math.ceil(Wash_1.reagent_volume / Wash_1.max_volume_allowed)
        wash_volume = Wash_1.reagent_volume / wash_trips #136.66
        wash_transfer_vol = []
        for i in range(wash_trips):
            wash_transfer_vol.append(wash_volume + Wash_1.disposal_volume)
        x_offset_rs = 2.5
        rinse = False # Not needed

        for i in range(num_cols):
            x_offset_source = 0
            x_offset_dest   = -1 * find_side(i) * x_offset_rs
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
                if TIP_RECYCLING_IN_WASH:
                    w1_tip_pos_list += [tip_track['tips'][m300][int(tip_track['counts'][m300] / 8)]]
            for transfer_vol in wash_transfer_vol:
                #Calculate pickup_height based on remaining volume and shape of container
                [pickup_height, change_col] = calc_height(Wash_1, multi_well_rack_area, transfer_vol*8)
                ctx.comment('Aspirando desde la columna del reservorio: ' + str(Wash_1.col))
                ctx.comment('La altura de recogida es ' + str(pickup_height))

                move_vol_multi(m300, reagent = Wash_1, source = Wash_1.reagent_reservoir[Wash_1.col],
                        dest = work_destinations[i], vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                        pickup_height = pickup_height, rinse = rinse, avoid_droplet = False, wait_time = 0, blow_out = False)
            
            if WASH_1_NUM_MIXES > 0:
                custom_mix(m300, Wash_1, location = work_destinations[i], vol = 180, two_thirds_mix_bottom = True,
                        rounds = WASH_1_NUM_MIXES, blow_out = False, mix_height = 1.5, offset = x_offset_dest)
            
            m300.move_to(work_destinations[i].top(0))
            m300.air_gap(Wash_1.air_gap_vol_bottom) #air gap

            if recycle_tip or TIP_RECYCLING_IN_WASH:
                m300.return_tip()
            else:
                m300.drop_tip(home_after = False)
            tip_track['counts'][m300] += 8

        log_step_end(start)
        ###############################################################################
        # STEP 5 ADD WASH
        ########

    ###############################################################################
    # STEP 6 Incubación con el imán ON
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        # switch on magnet
        magdeck.engage(mag_height)
        ctx.delay(seconds=STEPS[STEP]['wait_time'], msg='Incubación con el imán ON durante ' + format(STEPS[STEP]['wait_time']) + ' segundos.')

        log_step_end(start)
        ####################################################################
        # STEP 6 Incubación con el imán ON
        ########

    ###############################################################################
    # STEP 7 Desechar sobrenadante
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        supernatant_trips = math.ceil(Wash_1.reagent_volume / Wash_1.max_volume_allowed)
        supernatant_volume = Wash_1.max_volume_allowed # We try to remove an exceeding amount of supernatant to make sure it is empty
        supernatant_transfer_vol = []
        for i in range(supernatant_trips):
            supernatant_transfer_vol.append(supernatant_volume + Sample.disposal_volume)
        
        x_offset_rs = 2
        pickup_height = 0.5 # Original 0.5

        for i in range(num_cols):
            x_offset_source     = find_side(i) * x_offset_rs
            x_offset_dest       = 0
            drop_height         = 15
            not_first_transfer  = False

            if not m300.hw_pipette['has_tip']:
                if TIP_RECYCLING_IN_WASH:
                    pick_up(m300, w1_tip_pos_list[i])
                    m300.dispense(Wash_1.air_gap_vol_top, work_destinations[i].top(z = 0), rate = Wash_1.flow_rate_dispense)
                else:
                    pick_up(m300)
            for transfer_vol in supernatant_transfer_vol:
                #Pickup_height is fixed here
                ctx.comment('Aspirando de la columna del deepwell: ' + str(i+1))
                ctx.comment('La altura de recogida es ' + str(pickup_height) )
                move_vol_multi(m300, reagent = Sample, source = work_destinations[i],
                    dest = waste, vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                    pickup_height = pickup_height, rinse = False, avoid_droplet = False, wait_time = 2, blow_out = False,
                    dispense_bottom_air_gap_before = not_first_transfer, drop_height = waste_drop_height)
                m300.move_to(waste.top(z = waste_drop_height))
                m300.air_gap(Sample.air_gap_vol_bottom)
                not_first_transfer = True

            if recycle_tip:
                m300.return_tip()
            else:
                m300.drop_tip(home_after = False)
            if not TIP_RECYCLING_IN_WASH:
                tip_track['counts'][m300] += 8

        log_step_end(start)
        ###############################################################################
        # STEP 7 Desechar sobrenadante
        ########

    ###############################################################################
    # STEP 8 MAGNET OFF
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        # Imán OFF
        magdeck.disengage()

        log_step_end(start)
        ###############################################################################
        # STEP 8 MAGNET OFF
        ########

    ###############################################################################
    # STEP 9 Transferir segundo lavado
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        wash_trips = math.ceil(Wash_2.reagent_volume / Wash_2.max_volume_allowed)
        wash_volume = Wash_2.reagent_volume / wash_trips #136.66
        wash_transfer_vol = []
        for i in range(wash_trips):
            wash_transfer_vol.append(wash_volume + Wash_2.disposal_volume)
        x_offset_rs = 2.5
        rinse = False # Not needed

        for i in range(num_cols):
            x_offset_source = 0
            x_offset_dest   = -1 * find_side(i) * x_offset_rs
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
                if TIP_RECYCLING_IN_WASH:
                    w2_tip_pos_list += [tip_track['tips'][m300][int(tip_track['counts'][m300] / 8)]]
            for transfer_vol in wash_transfer_vol:
                #Calculate pickup_height based on remaining volume and shape of container
                [pickup_height, change_col] = calc_height(Wash_2, multi_well_rack_area, transfer_vol*8)
                ctx.comment('Aspirando desde la columna del reservorio: ' + str(Wash_2.col))
                ctx.comment('La altura de recogida es ' + str(pickup_height))

                move_vol_multi(m300, reagent = Wash_2, source = Wash_2.reagent_reservoir[Wash_2.col],
                        dest = work_destinations[i], vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                        pickup_height = pickup_height, rinse = rinse, avoid_droplet = False, wait_time = 0, blow_out = False)
            
            if WASH_2_NUM_MIXES > 0:
                custom_mix(m300, Wash_2, location = work_destinations[i], vol = 180, two_thirds_mix_bottom = True,
                        rounds = WASH_2_NUM_MIXES, blow_out = False, mix_height = 1.5, offset = x_offset_dest)
            
            m300.move_to(work_destinations[i].top(0))
            m300.air_gap(Wash_2.air_gap_vol_bottom) #air gap

            if recycle_tip or TIP_RECYCLING_IN_WASH:
                m300.return_tip()
            else:
                m300.drop_tip(home_after = False)
            tip_track['counts'][m300] += 8

        log_step_end(start)
        ###############################################################################
        # STEP 9 ADD WASH
        ########

    ###############################################################################
    # STEP 10 Incubación con el imán ON
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        # switch on magnet
        magdeck.engage(mag_height)
        ctx.delay(seconds=STEPS[STEP]['wait_time'], msg='Incubación con el imán ON durante ' + format(STEPS[STEP]['wait_time']) + ' segundos.')

        log_step_end(start)
        ####################################################################
        # STEP 10 Incubación con el imán ON
        ########

    ###############################################################################
    # STEP 11 Desechar sobrenadante
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        supernatant_trips = math.ceil(Wash_2.reagent_volume / Wash_2.max_volume_allowed)
        supernatant_volume = Wash_2.max_volume_allowed # We try to remove an exceeding amount of supernatant to make sure it is empty
        supernatant_transfer_vol = []
        for i in range(supernatant_trips):
            supernatant_transfer_vol.append(supernatant_volume + Sample.disposal_volume)
        
        x_offset_rs = 2
        pickup_height = 0.5 # Original 0.5

        for i in range(num_cols):
            x_offset_source = find_side(i) * x_offset_rs
            x_offset_dest   = 0
            not_first_transfer = False

            if not m300.hw_pipette['has_tip']:
                if TIP_RECYCLING_IN_WASH:
                    pick_up(m300, w2_tip_pos_list[i])
                    m300.dispense(Wash_2.air_gap_vol_top, work_destinations[i].top(z = 0), rate = Wash_2.flow_rate_dispense)
                else:
                    pick_up(m300)
            for transfer_vol in supernatant_transfer_vol:
                #Pickup_height is fixed here
                ctx.comment('Aspirando de la columna del deepwell: ' + str(i+1))
                ctx.comment('La altura de recogida es ' + str(pickup_height) )
                move_vol_multi(m300, reagent = Sample, source = work_destinations[i],
                    dest = waste, vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                    pickup_height = pickup_height, rinse = False, avoid_droplet = False, wait_time = 2, blow_out = False,
                    dispense_bottom_air_gap_before = not_first_transfer, drop_height = waste_drop_height)
                m300.move_to(waste.top(z = waste_drop_height))
                m300.air_gap(Sample.air_gap_vol_bottom)
                not_first_transfer = True

            if recycle_tip:
                m300.return_tip()
            else:
                m300.drop_tip(home_after = False)
            if not TIP_RECYCLING_IN_WASH:
                tip_track['counts'][m300] += 8

        log_step_end(start)
        ###############################################################################
        # STEP 11 Desechar sobrenadante
        ########

    ###############################################################################
    # STEP 12 ALLOW DRY
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        ctx.comment(' ')
        ctx.delay(seconds=STEPS[STEP]['wait_time'], msg='Dry for ' + format(STEPS[STEP]['wait_time']) + ' segundos.') # 
        ctx.comment(' ')

        log_step_end(start)
        ###############################################################################
        # STEP 12 ALLOW DRY
        ########

    ###############################################################################
    # STEP 13 MAGNET OFF
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        # Imán OFF
        magdeck.disengage()

        log_step_end(start)
        ###############################################################################
        # STEP 13 MAGNET OFF
        ########
    
    ###############################################################################
    # STEP 14 Transferir elución
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        elution_trips = math.ceil(Elution.reagent_volume / Elution.max_volume_allowed)
        elution_volume = Elution.reagent_volume / elution_trips
        elution_wash_vol = []
        for i in range(elution_trips):
            elution_wash_vol.append(elution_volume + Sample.disposal_volume)
        x_offset_rs = 2.5

        ########
        # Water or elution buffer
        for i in range(num_cols):
            x_offset_source = 0
            x_offset_dest   = -1 * find_side(i) * x_offset_rs # Original 0
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
                if TIP_RECYCLING_IN_ELUTION:
                    elution_tip_pos_list += [tip_track['tips'][m300][int(tip_track['counts'][m300] / 8)]]
            for transfer_vol in elution_wash_vol:
                #Calculate pickup_height based on remaining volume and shape of container
                [pickup_height, change_col] = calc_height(Elution, multi_well_rack_area, transfer_vol*8)
                ctx.comment('Aspirando desde la columna del reservorio: ' + str(Elution.col))
                ctx.comment('La altura de recogida es ' + str(pickup_height))

                move_vol_multi(m300, reagent = Elution, source = Elution.reagent_reservoir[Elution.col],
                        dest = work_destinations[i], vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                        pickup_height = pickup_height, rinse = False, avoid_droplet = False, wait_time = 0, blow_out = False, drop_height = -35)
            
            if ELUTION_NUM_MIXES > 0:
                ctx.comment(' ')
                ctx.comment('Mezclando muestra with Elution')
                custom_mix(m300, Elution, work_destinations[i], vol = Elution.reagent_volume, rounds = ELUTION_NUM_MIXES,
                    blow_out = False, mix_height = 1, offset = x_offset_dest, drop_height = -35)
            
            m300.move_to(work_destinations[i].top(0))
            m300.air_gap(Elution.air_gap_vol_bottom) #air gap
            
            if recycle_tip or TIP_RECYCLING_IN_ELUTION:
                m300.return_tip()
            else:
                m300.drop_tip(home_after = False)
            tip_track['counts'][m300] += 8
        log_step_end(start)
        ###############################################################################
        # STEP 14 Transferir elución
        ########

    ###############################################################################
    # STEP 15 Incubación con el imán ON
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        # switch on magnet
        magdeck.engage(mag_height)
        ctx.delay(seconds=STEPS[STEP]['wait_time'], msg='Incubación con el imán ON durante ' + format(STEPS[STEP]['wait_time']) + ' segundos.')

        log_step_end(start)
        ####################################################################
        # STEP 15 Incubación con el imán ON
        ########

    ###############################################################################
    # STEP 16 TRANSFER TO FINAL PLATES
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        elution_trips = math.ceil(ELUTION_FINAL_VOLUME_PER_SAMPLE / Elution.max_volume_allowed)
        elution_volume = ELUTION_FINAL_VOLUME_PER_SAMPLE / elution_trips
        elution_vol = []
        for i in range(elution_trips):
            elution_vol.append(elution_volume + Elution.disposal_volume)
        x_offset_rs = 2
        for i in range(num_cols):
            x_offset_source = find_side(i) * x_offset_rs
            x_offset_dest   = 0
            if not m300.hw_pipette['has_tip']:
                if TIP_RECYCLING_IN_ELUTION:
                    pick_up(m300, elution_tip_pos_list[i])
                    m300.dispense(Elution.air_gap_vol_top, work_destinations[i].top(z = 0), rate = Elution.flow_rate_dispense)
                else:
                    pick_up(m300)
            for transfer_vol in elution_vol:
                #Pickup_height is fixed here
                pickup_height = 1
                ctx.comment('Aspirando de la columna del deepwell: ' + str(i+1))
                ctx.comment('La altura de recogida es ' + str(pickup_height) )

                move_vol_multi(m300, reagent = Sample, source = work_destinations[i],
                        dest = final_destinations[i], vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                        pickup_height = pickup_height, rinse = False, avoid_droplet = False, wait_time = 0, blow_out = True, touch_tip = False,
                        drop_height = 3)

            m300.move_to(final_destinations[i].top(0))
            m300.air_gap(Sample.air_gap_vol_bottom) #air gap

            if recycle_tip:
                m300.return_tip()
            else:
                m300.drop_tip(home_after = False)
            if not TIP_RECYCLING_IN_ELUTION:
                tip_track['counts'][m300] += 8

        log_step_end(start)

        ###############################################################################
        # STEP 16 TRANSFER TO FINAL PLATES
        ########

    '''if not ctx.is_simulating():
        with open(file_path,'w') as outfile:
            json.dump(STEPS, outfile)'''

    magdeck.disengage()
    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('Homing robot')
    ctx.comment('###############################################')
    ctx.comment(' ')
    ctx.home()
###############################################################################
    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write('STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    
    finish_run()
def run(ctx: protocol_api.ProtocolContext):

    #Change light to red
    ctx._hw_manager.hardware.set_lights(button=(1, 0, 0))

    ctx.comment('Actual used columns: ' + str(num_cols))
    STEP = 0
    STEPS = {  #Dictionary with STEP activation, description, and times
        1: {
            'Execute': True,
            'description': 'Transferir solución con bolas magnéticas'
        },
        2: {
            'Execute': True,
            'description': 'Transferir solución de lavado'
        },
        3: {
            'Execute': True,
            'description': 'Transferir etanol'
        },
        4: {
            'Execute': True,
            'description': 'Transferir elución'
        }
    }

    #Folder and file_path for log time
    import os
    folder_path = '/var/lib/jupyter/notebooks/' + run_id
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/time_log.txt'

    #Define Reagents as objects with their properties
    class Reagent:
        def __init__(self,
                     name,
                     flow_rate_aspirate,
                     flow_rate_dispense,
                     flow_rate_aspirate_mix,
                     flow_rate_dispense_mix,
                     air_gap_vol_bottom,
                     air_gap_vol_top,
                     disposal_volume,
                     rinse,
                     max_volume_allowed,
                     reagent_volume,
                     reagent_reservoir_volume,
                     num_wells,
                     h_cono,
                     v_fondo,
                     tip_recycling='none',
                     dead_vol=DEFAULT_DEAD_VOL):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.flow_rate_aspirate_mix = flow_rate_aspirate_mix
            self.flow_rate_dispense_mix = flow_rate_dispense_mix
            self.air_gap_vol_bottom = air_gap_vol_bottom
            self.air_gap_vol_top = air_gap_vol_top
            self.disposal_volume = disposal_volume
            self.rinse = bool(rinse)
            self.max_volume_allowed = max_volume_allowed
            self.reagent_volume = reagent_volume
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.tip_recycling = tip_recycling
            self.dead_vol = dead_vol
            self.vol_well_original = (reagent_reservoir_volume / num_wells
                                      ) + dead_vol if num_wells > 0 else 0

    #Reagents and their characteristics
    Wash = Reagent(
        name='Wash',
        flow_rate_aspirate=25,  # Original = 0.5
        flow_rate_dispense=100,  # Original = 1
        flow_rate_aspirate_mix=
        1,  # Liquid density very high, needs slow aspiration
        flow_rate_dispense_mix=
        1,  # Liquid density very high, needs slow dispensation
        air_gap_vol_bottom=5,
        air_gap_vol_top=0,
        disposal_volume=1,
        rinse=True,
        max_volume_allowed=270,
        reagent_volume=
        WASH_VOLUME_PER_SAMPLE,  # reagent volume needed per sample
        reagent_reservoir_volume=(NUM_SAMPLES + 5) *
        WASH_VOLUME_PER_SAMPLE,  #70000, #51648
        num_wells=1,
        h_cono=1.95,
        v_fondo=695)  #1.95 * multi_well_rack_area / 2, #Prismatic

    Ethanol = Reagent(
        name='Ethanol',
        flow_rate_aspirate=25,
        flow_rate_dispense=100,
        flow_rate_aspirate_mix=1,
        flow_rate_dispense_mix=1,
        air_gap_vol_bottom=5,
        air_gap_vol_top=0,
        disposal_volume=1,
        rinse=True,
        max_volume_allowed=270,
        reagent_volume=ETHANOL_VOLUME_PER_SAMPLE,
        reagent_reservoir_volume=(NUM_SAMPLES + 5) * ETHANOL_VOLUME_PER_SAMPLE,
        num_wells=1,
        h_cono=1.95,
        v_fondo=695)  #1.95 * multi_well_rack_area / 2, #Prismatic

    Beads_PK_Binding = Reagent(
        name='Magnetic beads + PK + Binding',
        flow_rate_aspirate=0.5,
        flow_rate_dispense=0.5,
        flow_rate_aspirate_mix=0.5,
        flow_rate_dispense_mix=0.5,
        air_gap_vol_bottom=5,
        air_gap_vol_top=0,
        disposal_volume=1,
        rinse=True,
        max_volume_allowed=280,
        reagent_volume=BEADS_VOLUME_PER_SAMPLE,
        reagent_reservoir_volume=NUM_SAMPLES * BEADS_VOLUME_PER_SAMPLE * 1.1,
        num_wells=math.ceil(
            (NUM_SAMPLES * BEADS_VOLUME_PER_SAMPLE * 1.1) / 11500),
        h_cono=1.95,
        v_fondo=695)  #1.95 * multi_well_rack_area / 2, #Prismatic

    Elution = Reagent(
        name='Elution',
        flow_rate_aspirate=25,  # Original 0.5
        flow_rate_dispense=100,  # Original 1
        flow_rate_aspirate_mix=15,
        flow_rate_dispense_mix=25,
        air_gap_vol_bottom=5,
        air_gap_vol_top=0,
        disposal_volume=1,
        rinse=False,
        max_volume_allowed=270,
        reagent_volume=ELUTION_VOLUME_PER_SAMPLE,
        reagent_reservoir_volume=(NUM_SAMPLES + 5) * ELUTION_VOLUME_PER_SAMPLE,
        num_wells=math.ceil(
            (NUM_SAMPLES + 5) * ELUTION_VOLUME_PER_SAMPLE / 13000),
        h_cono=1.95,
        v_fondo=695)  #1.95 * multi_well_rack_area / 2, #Prismatic

    Sample = Reagent(
        name='Sample',
        flow_rate_aspirate=3,  # Original 0.5
        flow_rate_dispense=3,  # Original 1
        flow_rate_aspirate_mix=15,
        flow_rate_dispense_mix=25,
        air_gap_vol_bottom=5,
        air_gap_vol_top=0,
        disposal_volume=1,
        rinse=False,
        max_volume_allowed=150,
        reagent_volume=50,
        reagent_reservoir_volume=(NUM_SAMPLES + 5) * 50,  #14800,
        num_wells=num_cols,  #num_cols comes from available columns
        h_cono=4,
        v_fondo=4 * math.pi * 4**3 / 3)  #Sphere

    # Calculate Beads channel volumes
    def calc_beads_vol_well_original(beads):
        # Reajusting Channel volume to fit complete miti-tip dispense
        trips = math.ceil(BEADS_VOLUME_PER_SAMPLE / beads.max_volume_allowed)
        total_trips = math.ceil(NUM_SAMPLES / 8) * trips
        trip_vol = 8 * beads.max_volume_allowed * 1.1
        max_trips_per_well = math.floor(11500 / trip_vol)

        trips_per_well = math.ceil(total_trips / beads.num_wells)

        # Add an additional channel if needed
        if trips_per_well > max_trips_per_well:
            ctx.comment('Adding an additional beads channel')
            beads.num_wells = beads.num_wells + 1
            trips_per_well = math.ceil(total_trips / beads.num_wells)

        beads.vol_well_original = trips_per_well * trip_vol + DEFAULT_DEAD_VOL
        ctx.comment('Reajusting Bead Channels volume to: ' +
                    str(beads.vol_well_original) + ' ul')
        ctx.comment('Number of Beads channels: ' + str(beads.num_wells))
        beads.vol_well = beads.vol_well_original
        return beads.vol_well_original

    calc_beads_vol_well_original(Beads_PK_Binding)

    Wash.vol_well = Wash.vol_well_original
    Ethanol.vol_well = Ethanol.vol_well_original
    Beads_PK_Binding.vol_well = Beads_PK_Binding.vol_well_original
    Elution.vol_well = Elution.vol_well_original
    Sample.vol_well = 350  # Arbitrary value

    def str_rounded(num):
        return str(int(num + 0.5))

    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('VALORES DE VARIABLES')
    ctx.comment(' ')
    ctx.comment('Número de muestras: ' + str(NUM_SAMPLES) + ' (' +
                str(num_cols) + ' ' +
                ('columna' if num_cols == 1 else 'columnas') + ')')
    ctx.comment(' ')
    ctx.comment('Volumen de beads por muestra: ' +
                str(BEADS_VOLUME_PER_SAMPLE) + ' ul')
    ctx.comment('Volumen del lavado por muestra: ' +
                str(WASH_VOLUME_PER_SAMPLE) + ' ul')
    ctx.comment('Volumen del etanol por muestra: ' +
                str(ETHANOL_VOLUME_PER_SAMPLE) + ' ul')
    ctx.comment('Volumen de elución por muestra: ' +
                str(ELUTION_VOLUME_PER_SAMPLE) + ' ul')
    ctx.comment(' ')
    ctx.comment(
        'Número de mezclas en la primera recogida de un canal con bolas magnéticas: '
        + str(BEADS_WELL_FIRST_TIME_NUM_MIXES))
    ctx.comment(
        'Número de mezclas en el resto de recogidas de un canal con bolas magnéticas: '
        + str(BEADS_WELL_NUM_MIXES))
    ctx.comment('Número de mezclas con la solución de bolas magnéticas: ' +
                str(BEADS_NUM_MIXES))
    ctx.comment(' ')
    ctx.comment('Repeticiones del sonido final: ' + str(SOUND_NUM_PLAYS))
    ctx.comment('Foto-sensible: ' + str(PHOTOSENSITIVE))
    ctx.comment(' ')

    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('VOLÚMENES PARA ' + str(NUM_SAMPLES) + ' MUESTRAS')
    ctx.comment(' ')
    ctx.comment(
        'Beads + PK + Binding: ' + str(Beads_PK_Binding.num_wells) +
        (' canal' if Beads_PK_Binding.num_wells == 1 else ' canales') +
        ' desde el canal 2 en el reservorio de 12 canales con un volumen de ' +
        str_rounded(Beads_PK_Binding.vol_well_original) + ' uL cada uno')
    ctx.comment(
        'Elution: ' + str(Elution.num_wells) +
        (' canal' if Elution.num_wells == 1 else ' canales') +
        ' desde el canal 7 en el reservorio de 12 canales con un volumen de ' +
        str_rounded(Elution.vol_well_original) + ' uL cada uno')
    ctx.comment('Wash: en el reservorio 1 (slot 2) con un volumen de ' +
                str_rounded(Wash.vol_well_original) + ' uL')
    ctx.comment('Etanol: en el reservorio 2 (slot 3) con un volumen de ' +
                str_rounded(Ethanol.vol_well_original) + ' uL')
    ctx.comment('###############################################')
    ctx.comment(' ')

    ###################
    #Custom functions
    def custom_mix(pipet,
                   reagent,
                   location,
                   vol,
                   rounds,
                   blow_out,
                   mix_height,
                   offset,
                   wait_time=0,
                   drop_height=-1,
                   two_thirds_mix_bottom=False):
        '''
        Function for mix in the same location a certain number of rounds. Blow out optional. Offset
        can set to 0 or a higher/lower value which indicates the lateral movement
        '''
        if mix_height <= 0:
            mix_height = 1
        pipet.aspirate(1,
                       location=location.bottom(z=mix_height),
                       rate=reagent.flow_rate_aspirate_mix)
        for i in range(rounds):
            pipet.aspirate(vol,
                           location=location.bottom(z=mix_height),
                           rate=reagent.flow_rate_aspirate_mix)
            if two_thirds_mix_bottom and i < ((rounds / 3) * 2):
                pipet.dispense(vol,
                               location=location.bottom(z=5).move(
                                   Point(x=offset)),
                               rate=reagent.flow_rate_dispense_mix)
            else:
                pipet.dispense(vol,
                               location=location.top(z=drop_height).move(
                                   Point(x=offset)),
                               rate=reagent.flow_rate_dispense_mix)
        pipet.dispense(1,
                       location=location.bottom(z=mix_height),
                       rate=reagent.flow_rate_dispense_mix)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out
        if wait_time != 0:
            ctx.delay(seconds=wait_time,
                      msg='Waiting for ' + str(wait_time) + ' seconds.')

    def calc_height(reagent,
                    cross_section_area,
                    aspirate_volume,
                    min_height=0.4):
        nonlocal ctx
        ctx.comment('Remaining volume ' + str(reagent.vol_well) +
                    '< needed volume ' + str(aspirate_volume) + '?')
        if (reagent.vol_well - reagent.dead_vol) < aspirate_volume:
            ctx.comment('Next column should be picked')
            ctx.comment('Previous to change: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('After change: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('New volume:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume -
                      reagent.v_cono) / cross_section_area
            #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Remaining volume:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume -
                      reagent.v_cono) / cross_section_area
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Calculated height is ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('Used height is ' + str(height))
            col_change = False
        return height, col_change

    def move_vol_multi(pipet,
                       reagent,
                       source,
                       dest,
                       vol,
                       x_offset_source,
                       x_offset_dest,
                       pickup_height,
                       rinse,
                       avoid_droplet,
                       wait_time,
                       blow_out,
                       touch_tip=False,
                       drop_height=-5,
                       dispense_bottom_air_gap_before=False):
        # Rinse before aspirating
        if rinse == True:
            #pipet.aspirate(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap
            custom_mix(pipet,
                       reagent,
                       location=source,
                       vol=vol,
                       rounds=20,
                       blow_out=False,
                       mix_height=3,
                       offset=0)
            #pipet.dispense(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_dispense)

        # SOURCE
        if dispense_bottom_air_gap_before and reagent.air_gap_vol_bottom:
            pipet.dispense(reagent.air_gap_vol_bottom,
                           source.top(z=-2),
                           rate=reagent.flow_rate_dispense)

        if reagent.air_gap_vol_top != 0:  #If there is air_gap_vol, switch pipette to slow speed
            pipet.move_to(source.top(z=0))
            pipet.air_gap(reagent.air_gap_vol_top)  #air gap
            #pipet.aspirate(reagent.air_gap_vol_top, source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap

        s = source.bottom(pickup_height).move(Point(x=x_offset_source))
        pipet.aspirate(vol, s,
                       rate=reagent.flow_rate_aspirate)  # aspirate liquid

        if reagent.air_gap_vol_bottom != 0:  #If there is air_gap_vol, switch pipette to slow speed
            pipet.air_gap(reagent.air_gap_vol_bottom, height=0)  #air gap

        if wait_time != 0:
            ctx.delay(seconds=wait_time,
                      msg='Waiting for ' + str(wait_time) + ' seconds.')

        if avoid_droplet == True:  # Touch the liquid surface to avoid droplets
            ctx.comment("Moving to: " + str(round(pickup_height, 2)) + ' mm')
            pipet.move_to(source.bottom(pickup_height))

        # GO TO DESTINATION
        d = dest.top(z=drop_height).move(Point(x=x_offset_dest))
        pipet.dispense(vol - reagent.disposal_volume +
                       reagent.air_gap_vol_bottom,
                       d,
                       rate=reagent.flow_rate_dispense)

        if wait_time != 0:
            ctx.delay(seconds=wait_time,
                      msg='Waiting for ' + str(wait_time) + ' seconds.')

        if reagent.air_gap_vol_top != 0:
            pipet.dispense(reagent.air_gap_vol_top,
                           dest.top(z=0),
                           rate=reagent.flow_rate_dispense)

        if blow_out == True:
            pipet.blow_out(dest.top(z=drop_height))

        if touch_tip == True:
            pipet.touch_tip(speed=20, v_offset=-10, radius=0.7)

        #if reagent.air_gap_vol_bottom != 0:
        #pipet.move_to(dest.top(z = 0))
        #pipet.air_gap(reagent.air_gap_vol_bottom) #air gap
        #pipet.aspirate(air_gap_vol_bottom, dest.top(z = 0),rate = reagent.flow_rate_aspirate) #air gap

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        #if not ctx.is_simulating():
        if tip_track['counts'][pip] >= tip_track['maxes'][pip]:
            ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \
            resuming.')
            pip.reset_tipracks()
            tip_track['counts'][pip] = 0
        pip.pick_up_tip()

    ##########
    def start_run():
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Empezando protocolo')
        if PHOTOSENSITIVE == False:
            ctx._hw_manager.hardware.set_lights(button=True, rails=True)
        else:
            ctx._hw_manager.hardware.set_lights(button=True, rails=False)
        now = datetime.now()

        # dd/mm/YY H:M:S
        start_time = now.strftime("%Y/%m/%d %H:%M:%S")
        return start_time

    def run_quiet_process(command):
        subprocess.check_output('{} &> /dev/null'.format(command), shell=True)

    def play_sound(filename):
        print('Speaker')
        print('Next\t--> CTRL-C')
        try:
            run_quiet_process('mpg123 {}'.format(path_sounds + filename +
                                                 '.mp3'))
            run_quiet_process('mpg123 {}'.format(path_sounds + sonido_defecto))
            run_quiet_process('mpg123 {}'.format(path_sounds + filename +
                                                 '.mp3'))
        except KeyboardInterrupt:
            pass
            print()

    def finish_run(switch_off_lights=False):
        ctx.comment('###############################################')
        ctx.comment('Protocolo finalizado')
        ctx.comment(' ')
        #Set light color to blue
        ctx._hw_manager.hardware.set_lights(button=True, rails=False)
        now = datetime.now()
        # dd/mm/YY H:M:S
        finish_time = now.strftime("%Y/%m/%d %H:%M:%S")
        if PHOTOSENSITIVE == False:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button=False, rails=False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button=True, rails=True)
                time.sleep(0.3)
        else:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button=False, rails=False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button=True, rails=False)
                time.sleep(0.3)
        if switch_off_lights:
            ctx._hw_manager.hardware.set_lights(button=True, rails=False)

        # TODO: Añadir refills a los tip_racks
        # used_tips = tip_track['num_refills'][m300] * 96 * len(m300.tip_racks) + tip_track['counts'][m300]
        ctx.comment('Puntas de 300 uL utilizadas: ' +
                    str(tip_track['counts'][m300]) + ' (' +
                    str(round(tip_track['counts'][m300] / 96, 2)) +
                    ' caja(s))')
        ctx.comment('###############################################')

        if not ctx.is_simulating():
            for i in range(SOUND_NUM_PLAYS):
                if i > 0:
                    time.sleep(60)
                play_sound('finished_process_esp')

        return finish_time

    ##########
    def find_side(col):
        if col % 2 == 0:
            side = -1  # left
        else:
            side = 1  # right
        return side

# load labware and modules
############################################
######## Sample plate - comes from A

    deepwell_plate_samples = ctx.load_labware('nest_96_wellplate_2ml_deep',
                                              '1',
                                              'NEST 96 Deepwell Plate 2mL 1')
    deepwell_plate_wash = ctx.load_labware('nest_96_wellplate_2ml_deep', '5',
                                           'NEST 96 Deepwell Plate 2mL 2')
    deepwell_plate_ethanol = ctx.load_labware('nest_96_wellplate_2ml_deep',
                                              '6',
                                              'NEST 96 Deepwell Plate 2mL 3')
    deepwell_plate_elution = ctx.load_labware(
        'biorad_96_wellplate_200ul_pcr', '7',
        'Bio-Rad 96 Well Plate 200 µL PCR')

    ####################################
    ######## 12 well rack
    reagent_multi_res = ctx.load_labware('nest_12_reservoir_15ml', '4',
                                         'Reagent deepwell plate')

    ####################################
    ######## Single reservoirs
    reagent_res_1 = ctx.load_labware('nest_1_reservoir_195ml', '2',
                                     'Single reagent reservoir 1')
    res_1 = reagent_res_1.wells()[0]

    reagent_res_2 = ctx.load_labware('nest_1_reservoir_195ml', '3',
                                     'Single reagent reservoir 2')
    res_2 = reagent_res_2.wells()[0]

    ####################################
    ######### Load tip_racks
    tips300 = [
        ctx.load_labware('opentrons_96_tiprack_300ul', slot,
                         '300µl filter tiprack') for slot in ['8', '9']
    ]

    ###############################################################################
    #Declare which reagents are in each reservoir as well as deepwell and sample plate
    Wash.reagent_reservoir = res_1
    Ethanol.reagent_reservoir = res_2
    Beads_PK_Binding.reagent_reservoir = reagent_multi_res.rows()[0][1:5]
    Elution.reagent_reservoir = reagent_multi_res.rows()[0][6:7]
    work_destinations = deepwell_plate_samples.rows()[0][:Sample.num_wells]
    wash_destinations = deepwell_plate_wash.rows()[0][:Sample.num_wells]
    ethanol_destinations = deepwell_plate_ethanol.rows()[0][:Sample.num_wells]
    elution_destinations = deepwell_plate_elution.rows()[0][:Sample.num_wells]

    # pipettes.
    m300 = ctx.load_instrument('p300_multi_gen2', 'right',
                               tip_racks=tips300)  # Load multi pipette

    #### used tip counter and set maximum tips available
    tip_track = {
        'counts': {
            m300: 0
        },
        'maxes': {
            m300: 96 * len(m300.tip_racks)
        }  #96 tips per tiprack * number or tipracks in the layout
    }

    ###############################################################################

    ###############################################################################
    start_run()

    ###############################################################################
    # STEP 1 TRANSFER BEADS + PK + Binding
    ########
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        beads_trips = math.ceil(Beads_PK_Binding.reagent_volume /
                                Beads_PK_Binding.max_volume_allowed)

        beads_volume = Beads_PK_Binding.reagent_volume / beads_trips  #136.66
        ctx.comment('bead_trips= ' + str(beads_trips))
        ctx.comment('beads_volume= ' + str(beads_volume) +
                    'ul  --> trip volume')
        beads_transfer_vol = []
        for i in range(beads_trips):
            ctx.comment('Trip ' + str(i) + ' --> ' +
                        str(beads_volume + Beads_PK_Binding.disposal_volume) +
                        ' ul')
            beads_transfer_vol.append(beads_volume +
                                      Beads_PK_Binding.disposal_volume)
        x_offset_source = 0
        x_offset_dest = 0
        rinse = False  # Original: True
        first_mix_done = False

        for i in range(num_cols):
            not_first_transfer = False

            ctx.comment("Column: " + str(i))
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(beads_transfer_vol):
                #Calculate pickup_height based on remaining volume and shape of container
                [pickup_height,
                 change_col] = calc_height(Beads_PK_Binding,
                                           multi_well_rack_area,
                                           transfer_vol * 8)
                if change_col == True or not first_mix_done:  #If we switch column because there is not enough volume left in current reservoir column we mix new column
                    ctx.comment('Mixing new reservoir column: ' +
                                str(Beads_PK_Binding.col))
                    custom_mix(m300,
                               Beads_PK_Binding,
                               Beads_PK_Binding.reagent_reservoir[
                                   Beads_PK_Binding.col],
                               vol=BEADS_MIX_VOLUME,
                               rounds=BEADS_WELL_FIRST_TIME_NUM_MIXES,
                               blow_out=False,
                               mix_height=0.5,
                               offset=0)
                    first_mix_done = True
                else:
                    ctx.comment('Mixing reservoir column: ' +
                                str(Beads_PK_Binding.col))
                    custom_mix(m300,
                               Beads_PK_Binding,
                               Beads_PK_Binding.reagent_reservoir[
                                   Beads_PK_Binding.col],
                               vol=BEADS_MIX_VOLUME,
                               rounds=BEADS_WELL_NUM_MIXES,
                               blow_out=False,
                               mix_height=0.5,
                               offset=0)
                ctx.comment('Aspirate from reservoir column: ' +
                            str(Beads_PK_Binding.col))
                ctx.comment('Pickup height is ' +
                            str(round(pickup_height, 2)) + ' mm')
                #if j!=0:
                #    rinse = False
                move_vol_multi(
                    m300,
                    reagent=Beads_PK_Binding,
                    source=Beads_PK_Binding.reagent_reservoir[
                        Beads_PK_Binding.col],
                    dest=work_destinations[i],
                    vol=transfer_vol,
                    x_offset_source=x_offset_source,
                    x_offset_dest=x_offset_dest,
                    pickup_height=pickup_height,
                    rinse=rinse,
                    avoid_droplet=False,
                    wait_time=2,
                    blow_out=False,
                    touch_tip=False,
                    drop_height=5,
                    dispense_bottom_air_gap_before=not_first_transfer)

                m300.air_gap(Beads_PK_Binding.air_gap_vol_bottom, height=5)
                not_first_transfer = True

            ctx.comment(' ')
            ctx.comment('Mixing sample ')
            custom_mix(m300,
                       Beads_PK_Binding,
                       location=work_destinations[i],
                       vol=BEADS_MIX_VOLUME,
                       rounds=BEADS_NUM_MIXES,
                       blow_out=False,
                       mix_height=0,
                       offset=0,
                       wait_time=2,
                       two_thirds_mix_bottom=True)
            m300.air_gap(Beads_PK_Binding.air_gap_vol_bottom,
                         height=0)  #air gap

            if recycle_tip == True:
                m300.return_tip()
            else:
                m300.drop_tip(home_after=False)
            tip_track['counts'][m300] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)
        ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300]))
        ###############################################################################
        # STEP 1 TRANSFER BEADS + PK + Binding
        ########

    ###############################################################################
    # STEP 2 TRANSFER WASH
    ########
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        wash_trips = math.ceil(Wash.reagent_volume / Wash.max_volume_allowed)
        wash_volume = Wash.reagent_volume / wash_trips  #136.66
        wash_transfer_vol = []
        for i in range(wash_trips):
            wash_transfer_vol.append(wash_volume + Wash.disposal_volume)
        x_offset_source = 0
        x_offset_dest = 0
        rinse = False
        for i in range(num_cols):
            ctx.comment("Column: " + str(i))
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)

            for j, transfer_vol in enumerate(wash_transfer_vol):
                ctx.comment('Aspirate from reservoir 1')
                #if j!=0:
                #    rinse = False
                move_vol_multi(m300,
                               reagent=Wash,
                               source=Wash.reagent_reservoir,
                               dest=wash_destinations[i],
                               vol=transfer_vol,
                               x_offset_source=x_offset_source,
                               x_offset_dest=x_offset_dest,
                               pickup_height=2,
                               rinse=rinse,
                               avoid_droplet=False,
                               wait_time=0,
                               blow_out=True,
                               touch_tip=False)
            ctx.comment(' ')
        if recycle_tip == True:
            m300.return_tip()
        else:
            m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)
        ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300]))
        ###############################################################################
        # STEP 2 TRANSFER WASH
        ########

    ###############################################################################
    # STEP 3 TRANSFER ETHANOL
    ########
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        ethanol_trips = math.ceil(Ethanol.reagent_volume /
                                  Ethanol.max_volume_allowed)
        ethanol_volume = Ethanol.reagent_volume / ethanol_trips  #136.66
        ethanol_transfer_vol = []
        for i in range(ethanol_trips):
            ethanol_transfer_vol.append(ethanol_volume +
                                        Ethanol.disposal_volume)
        x_offset_source = 0
        x_offset_dest = 0
        rinse = False
        for i in range(num_cols):
            ctx.comment("Column: " + str(i))
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)

            for j, transfer_vol in enumerate(ethanol_transfer_vol):
                ctx.comment('Aspirate from reservoir 2')
                #if j!=0:
                #    rinse = False
                move_vol_multi(m300,
                               reagent=Ethanol,
                               source=Ethanol.reagent_reservoir,
                               dest=ethanol_destinations[i],
                               vol=transfer_vol,
                               x_offset_source=x_offset_source,
                               x_offset_dest=x_offset_dest,
                               pickup_height=2,
                               rinse=rinse,
                               avoid_droplet=False,
                               wait_time=0,
                               blow_out=True,
                               touch_tip=False)

        if recycle_tip == True:
            m300.return_tip()
        else:
            m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)
        ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300]))
        ###############################################################################
        # STEP 3 TRANSFER ETHANOL
        ########

    ###############################################################################
    # STEP 4 TRANSFER ELUTION
    ########
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        elution_trips = math.ceil(Elution.reagent_volume /
                                  Elution.max_volume_allowed)
        elution_volume = Elution.reagent_volume / elution_trips  #136.66
        elution_transfer_vol = []
        for i in range(elution_trips):
            elution_transfer_vol.append(elution_volume +
                                        Elution.disposal_volume)
        x_offset_source = 0
        x_offset_dest = 0
        rinse = False

        for i in range(num_cols):
            ctx.comment("Column: " + str(i))
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(elution_transfer_vol):
                #Calculate pickup_height based on remaining volume and shape of container
                [pickup_height,
                 change_col] = calc_height(Elution, multi_well_rack_area,
                                           transfer_vol * 8)
                ctx.comment('Aspirate from reservoir column: ' +
                            str(Elution.col))
                ctx.comment('Pickup height is ' +
                            str(round(pickup_height, 2)) + ' mm')
                #if j!=0:
                #    rinse = False
                move_vol_multi(m300,
                               reagent=Elution,
                               source=Elution.reagent_reservoir[Elution.col],
                               dest=elution_destinations[i],
                               vol=transfer_vol,
                               x_offset_source=x_offset_source,
                               x_offset_dest=x_offset_dest,
                               pickup_height=pickup_height,
                               rinse=rinse,
                               avoid_droplet=False,
                               wait_time=0,
                               blow_out=False,
                               touch_tip=False)
            ctx.comment(' ')

        if recycle_tip == True:
            m300.return_tip()
        else:
            m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)
        ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300]))
        ###############################################################################
        # STEP 4 TRANSFER ELUTION
        ########

    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('Homing robot')
    ctx.comment('###############################################')
    ctx.comment(' ')
    ctx.home()
    ###############################################################################
    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write(
                'STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    finish_run(switch_off_lights)
Example #10
0
def run(protocol: protocol_api.ProtocolContext):
    if debug: print(protocol)

    tiprack_300 = protocol.load_labware('opentrons_96_tiprack_300ul',
                                        labwarePositions.tiprack_300,
                                        "tiprack 300ul")

    if debug: print(tiprack_300)
    pipette_300 = protocol.load_instrument(
        'p300_single_gen2' if pipette_300_GEN == 'GEN2' else 'p300_single',
        pipette_300_location,
        tip_racks=[tiprack_300])
    #    pipette_300 = protocol.load_instrument('p300_single', 'right', tip_racks=[tiprack_300])
    pipette_300.flow_rate.dispense = default_flow_rate
    pipette_300.flow_rate.aspirate = default_flow_rate
    pipette_300.starting_tip = tiprack_300.wells()[tiprack_300_starting_pos -
                                                   1]
    # pipette_300.starting_tip = tiprack_300.well(tiprack_starting_pos['tiprack_300'])

    if debug: print(pipette_300)

    black_96 = protocol.load_labware(type_of_96well_plate,
                                     labwarePositions.antibodies_plate,
                                     type_of_96well_plate)

    trough12 = protocol.load_labware('parhelia_12trough',
                                     labwarePositions.buffers_reservoir,
                                     '12-trough buffers reservoir')
    if (not retrieval):
        par2 = protocol.load_labware(omniStainer_type, labwarePositions.par2,
                                     omniStainer_type)
    if retrieval:
        temp_mod = protocol.load_module('temperature module',
                                        labwarePositions.heatmodule)

        par2 = temp_mod.load_labware(omniStainer_type)
    wellslist = list(par2.wells_by_name().keys())
    wellslist = wellslist[1:num_samples + 1]
    if debug: print(par2)

    buffer_wells = trough12.wells_by_name()

    buffers = Object()
    buffers.retrieval = buffer_wells['A1']
    buffers.TBS_wash = buffer_wells['A2']
    buffers.water = buffer_wells['A3']
    buffers.storage = buffer_wells['A4']
    buffers.eth_70perc_ = buffer_wells['A5']
    buffers.eth_80perc = buffer_wells['A6']
    buffers.eth_95perc = buffer_wells['A7']
    buffers.eth_100perc = buffer_wells['A8']
    buffers.hematoxylin = buffer_wells['A12']

    preblock_wells = black_96.rows()[0]
    antibody_wells = black_96.rows()[5]
    enzymeblock_wells = black_96.rows()[1]
    hrpsecondaryab_wells = black_96.rows()[2]
    substrate_wells = black_96.rows()[3]
    DAB_wells = black_96.rows()[4]

    sample_chambers = []

    for well in wellslist:
        sample_chambers.append(par2.wells_by_name()[well])

    if debug: print(sample_chambers)

    #################PROTOCOL####################
    protocol.home()

    if lid == 'yes':
        openPar2(protocol, pipette_300, par2)

    if retrieval:
        washSamples(pipette_300, buffers.retrieval, buffers.retrieval, 2, 1,
                    extra_bottom_gap + 18)
        washSamples(pipette_300, buffers.retrieval, sample_chambers,
                    wash_volume, 2, extra_bottom_gap)

        if lid == 'yes':
            closePar2(protocol, pipette_300, par2)

        temp_mod.set_temperature(95)
        print("retrieval")
        protocol.delay(minutes=15)
        #        washSamples(pipette_300, buffers.retrieval, sample_chambers, wash_volume, 1, extra_bottom_gap)
        protocol.delay(minutes=15)
        #        washSamples(pipette_300, buffers.retrieval, sample_chambers, wash_volume, 1, extra_bottom_gap)
        protocol.delay(minutes=15)
        print("cooling down to RT")
        temp_mod.set_temperature(25)
        protocol.delay(minutes=20)
        if lid == 'yes':
            openPar2(protocol, pipette_300, par2)

    # WASHING SAMPLES WITH TBS
    print("washing in TBS")
    washSamples(pipette_300, buffers.TBS_wash, buffers.TBS_wash, 2, 1,
                extra_bottom_gap + 18)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, 2,
                extra_bottom_gap)

    # Preblocking
    print("preblocking")
    print(len(wellslist))

    print("puncturing preblock wells")
    for i in range(len(wellslist)):
        print(i)
        washSamples(pipette_300,
                    preblock_wells[i],
                    preblock_wells[i],
                    2,
                    1,
                    extra_bottom_gap + 18,
                    keep_tip=True)
    pipette_300.drop_tip()

    print("applying the preblock")
    for i in range(len(wellslist)):
        print(i)
        washSamples(pipette_300, preblock_wells[i], sample_chambers[i],
                    ab_volume, 1, extra_bottom_gap)
    print("preblocking incubation: 15 min")
    protocol.delay(minutes=15)

    # APPLYING ANTIBODY COCKTAILS TO SAMPLES

    print("puncturing and applying abs")
    for i in range(len(wellslist)):
        print(i)
        washSamples(pipette_300,
                    antibody_wells[i],
                    antibody_wells[i],
                    2,
                    1,
                    extra_bottom_gap + 18,
                    keep_tip=True)
        washSamples(pipette_300, antibody_wells[i], sample_chambers[i],
                    ab_volume, 1, extra_bottom_gap)

    if lid == 'yes':
        closePar2(protocol, pipette_300, par2)

    # INCUBATE FOR DESIRED TIME
    print("staining incubation: " + str(primary_ab_incubation_time_minutes) +
          "min")
    protocol.delay(minutes=primary_ab_incubation_time_minutes)

    if lid == 'yes':
        openPar2(protocol, pipette_300, par2)

    # WASHING SAMPLES WITH TBS
    # three individual repeats below is because they need particular incubation time between them
    print("washing with TBS")
    for i in range(5):
        washSamples(pipette_300, buffers.TBS_wash, sample_chambers,
                    wash_volume, 1, extra_bottom_gap)
        protocol.delay(minutes=3)

    # APPLYING enzyme blocking
    print("puncturing enzyme blocking wells")
    for i in range(len(wellslist)):
        washSamples(pipette_300,
                    enzymeblock_wells[i],
                    enzymeblock_wells[i],
                    2,
                    1,
                    extra_bottom_gap + 18,
                    keep_tip=True)
    pipette_300.drop_tip()

    print("applying enzyme blocking")
    for i in range(len(wellslist)):
        washSamples(pipette_300, enzymeblock_wells[i], sample_chambers[i],
                    ab_volume, 1, extra_bottom_gap)
    # INCUBATE 10 MIN
    print("hrp blocking incubation: 10min")
    protocol.delay(minutes=10)

    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, 3,
                extra_bottom_gap)

    # APPLYING HRP SECONDARY ANTIBODY COCKTAILS TO SAMPLES
    print("puncturing hrpsecondaryab wells")
    for i in range(len(wellslist)):
        washSamples(pipette_300,
                    hrpsecondaryab_wells[i],
                    hrpsecondaryab_wells[i],
                    2,
                    1,
                    extra_bottom_gap + 18,
                    keep_tip=True)
    pipette_300.drop_tip()

    print("applying hrpsecondaryab")
    for i in range(len(wellslist)):
        washSamples(pipette_300, hrpsecondaryab_wells[i], sample_chambers[i],
                    ab_volume, 1, extra_bottom_gap)
    if lid == 'yes':
        closePar2(protocol, pipette_300, par2)

    # INCUBATE FOR DESIRED TIME
    print("staining incubation: " + str(secondary_ab_incubation_time_minutes) +
          "min")
    protocol.delay(minutes=secondary_ab_incubation_time_minutes)

    if lid == 'yes':
        openPar2(protocol, pipette_300, par2)

    # three individual repeats below is because they need particular incubation time between them
    print("washing with TBS")
    for i in range(3):
        washSamples(pipette_300, buffers.TBS_wash, sample_chambers,
                    wash_volume, 1, extra_bottom_gap)
        protocol.delay(minutes=3)

    # DILUTING AND APPLYING THE DAB
    print("puncturing the DAB wells")
    for i in range(len(wellslist)):
        washSamples(pipette_300, DAB_wells[i], DAB_wells[i], 2, 1,
                    extra_bottom_gap + 18)
    print("puncturing the substrate wells")
    for i in range(len(wellslist)):
        washSamples(pipette_300,
                    substrate_wells[i],
                    substrate_wells[i],
                    2,
                    1,
                    extra_bottom_gap + 18,
                    keep_tip=True)
    pipette_300.drop_tip()

    print("applying DAB")
    for i in range(len(wellslist)):
        dilute_and_apply_fixative(pipette_300, DAB_wells[i],
                                  substrate_wells[i], sample_chambers[i],
                                  wash_volume)

    print("developing substrate")

    protocol.delay(minutes=10)

    washSamples(pipette_300, buffers.water, buffers.water, 2, 1,
                extra_bottom_gap + 18)
    washSamples(pipette_300, buffers.water, sample_chambers, wash_volume, 5,
                extra_bottom_gap)
    if lid == 'yes':
        closePar2(protocol, pipette_300, par2)
Example #11
0
def run(protocol: protocol_api.ProtocolContext):
    """
    Aliquoting water from a 5mL screwcap tube into a 96wells plate
    """
    # =============================================================================

    # ======================LOADING LABWARE AND PIPETTES===========================
    # =============================================================================
    ## For available labware see "labware/list_of_available_labware".       ##
    plate_96 = protocol.load_labware(
        'biorad_96_wellplate_200ul_pcr',  #labware definition
        1,  #deck position
        'plate_96')  #custom name
    #### !!! OPTION 1: ROBOT
    tubes_5mL = protocol.load_labware(
        'eppendorfscrewcap_15_tuberack_5000ul',  #labware def
        2,  #deck position
        '5mL_tubes')  #custom name
    # #### !!! OPTION 2: SIMULATOR
    #  with open("labware/eppendorfscrewcap_15_tuberack_5000ul/"
    #            "eppendorfscrewcap_15_tuberack_5000ul.json") as labware_file:
    #          labware_def_5mL = json.load(labware_file)
    #  tubes_5mL = protocol.load_labware_from_definition(
    #          labware_def_5mL,   #variable derived from opening json
    #          2,                 #deck position
    #          '5mL_tubes')       #custom name
    tips_200 = protocol.load_labware(
        'opentrons_96_filtertiprack_200ul',  #labware definition
        3,  #deck position
        'tips_200')  #custom name

    ##### Loading pipettes
    p300 = protocol.load_instrument(
        'p300_single_gen2',  #instrument definition
        'right',  #mount position
        tip_racks=[tips_200])  #assigned tiprack
    # =============================================================================

    # ==========================VARIABLES TO SET#!!!===============================
    # =============================================================================
    start_vol_1 = 5000
    start_vol_2 = 2200
    ## The start_vol is the volume (ul) that is in the source labware at  ##
    ## the start of the protocol.                                         ##

    p300.starting_tip = tips_200.well('E3')
    ## The starting_tip is the location of first pipette tip in the box   ##
    # =============================================================================

    # ==========================PREDIFINED VARIABLES===============================
    # =============================================================================
    container = 'tube_5mL'
    # =============================================================================
    ##### Variables for volume tracking
    start_height_1 = cal_start_height(container, start_vol_1)
    start_height_2 = cal_start_height(container, start_vol_2)

    # =============================================================================

    # ===============================PROTOCOL======================================
    # =============================================================================
    # # Full 5mL tube
    # current_height = start_height_1
    # for i, well in enumerate(plate_96.wells()):
    #     if i == 0:
    #         p300.pick_up_tip()
    #         ## Then, after every 8th well, drop tip and pick up a new one.   ##
    #     elif i % 8 == 0:
    #         p300.drop_tip()
    #         p300.pick_up_tip()

    #     current_height, pip_height, bottom_reached = volume_tracking(
    #             container, 100, current_height)
    #       ## Make sure that the pipette tip is always submerged by setting   ##
    #       ## the current height 2 mm below its actual height                 ##
    #     if bottom_reached:
    #         protocol.home()
    #         protocol.pause("the tube is empty!")
    #     else:
    #         aspiration_location = tubes_5mL['C1'].bottom(pip_height)
    #       ## Set the location of where to aspirate from. Because we put this ##
    #       ## in the loop, the location will change to the newly calculated   ##
    #       ## height after each pipetting step.                               ##
    #       ## If the level of the liquid in the next run of the loop will be  ##
    #       ## smaller than 1 we have reached the bottom of the tube.          ##
    #         p300.aspirate(100, aspiration_location)
    #       ## Aspirate 200µL from the set aspiration location                 ##
    #         p300.dispense(100, well.top(z=-2))
    #       ## Dispense 200µL in the destination wel                           ##

    # protocol.pause('change plate')

    # 2200µL in  5mL tube
    current_height = start_height_2
    for i, well in enumerate(plate_96.wells()):
        if i == 0:
            p300.pick_up_tip()
            ## Then, after every 8th well, drop tip and pick up a new one.   ##
        elif i % 8 == 0:
            p300.drop_tip()
            p300.pick_up_tip()

        current_height, pip_height, bottom_reached = volume_tracking(
            container, 100, current_height)
        ## Make sure that the pipette tip is always submerged by setting   ##
        ## the current height 2 mm below its actual height                 ##
        if bottom_reached:
            protocol.home()
            protocol.pause("the tube is empty!")
        else:
            aspiration_location = tubes_5mL['C2'].bottom(pip_height)
            ## Set the location of where to aspirate from. Because we put this ##
            ## in the loop, the location will change to the newly calculated   ##
            ## height after each pipetting step.                               ##
            ## If the level of the liquid in the next run of the loop will be  ##
            ## smaller than 1 we have reached the bottom of the tube.          ##
            p300.aspirate(100, aspiration_location)
            ## Aspirate 200µL from the set aspiration location                 ##
            p300.dispense(100, well.top(z=-2))
Example #12
0
def run(ctx: protocol_api.ProtocolContext):
    import os
    ctx.comment('Actual used columns: ' + str(num_cols))

    # Define the STEPS of the protocol
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description, and times
        1: {'Execute': False, 'description': 'Make MMIX'},
        2: {'Execute': False, 'description': 'Transfer MMIX with P300'},
        3: {'Execute': True, 'description': 'Transfer MMIX with P20'},
        4: {'Execute': True, 'description': 'Clean up NC and PC wells'},
        5: {'Execute': True, 'description': 'Transfer elution'},
        6: {'Execute': True, 'description': 'Transfer PC'},
        7: {'Execute': True, 'description': 'Transfer NC'}
    }

    if STEPS[2]['Execute']==True:
        STEPS[3]['Execute']=False # just to make sure if P300 is being used for the MMIX, do not load P20 single

    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0

    #Folder and file_path for log time
    folder_path = '/var/lib/jupyter/notebooks/'+str(run_id)
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/KC_qPCR_time_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse,
                     reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo,
                      tip_recycling = 'none'):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.rinse = bool(rinse)
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.delay = delay
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.unused=[]
            self.tip_recycling = tip_recycling
            self.vol_well_original = reagent_reservoir_volume / num_wells


    # Reagents and their characteristics
    MMIX = Reagent(name = 'Master Mix',
                      rinse = False,
                      flow_rate_aspirate = 2,
                      flow_rate_dispense = 4,
                      reagent_reservoir_volume = $MMIX_total_volume, # volume_mmix_available,
                      num_wells = 1, #change with num samples
                      delay = 0,
                      h_cono = h_cone,
                      v_fondo = volume_cone  # V cono
                      )

    NC = Reagent(name = 'Negative control',
                      rinse = False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      reagent_reservoir_volume = 1000, # volume_mmix_available,
                      num_wells = 1, #change with num samples
                      delay = 0,
                      h_cono = h_cone,
                      v_fondo = volume_cone  # V cono
                      )

    PC = Reagent(name = 'Positive control',
                      rinse = False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      reagent_reservoir_volume = 1000, # volume_mmix_available,
                      num_wells = 1, #change with num samples
                      delay = 0,
                      h_cono = h_cone,
                      v_fondo = volume_cone  # V cono
                      )

    Elution = Reagent(name='Elution',
                      rinse=False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 2,
                      reagent_reservoir_volume=50,
                      delay=1,
                      num_wells=num_cols,  # num_cols comes from available columns
                      h_cono=0,
                      v_fondo=0
                      )
    tackpath = Reagent(name = 'MMIX_multiplex_tackpath',
                      rinse = False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      reagent_reservoir_volume = 1000,
                      num_wells = 1, #change with num samples
                      delay = 0,
                      h_cono = h_cone,
                      v_fondo = volume_cone  # V cono
                      )

    covid_assay = Reagent(name = 'Covid19_assay',
                      rinse = False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      reagent_reservoir_volume = 1000,
                      num_wells = 1, #change with num samples
                      delay = 0,
                      h_cono = h_cone,
                      v_fondo = volume_cone  # V cono
                      )

    mmix_water = Reagent(name = 'nuclease_free_water',
                      rinse = False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      reagent_reservoir_volume = 1000,
                      num_wells = 1, #change with num samples
                      delay = 0,
                      h_cono = h_cone,
                      v_fondo = volume_cone  # V cono
                      )

    component4 = Reagent(name = 'component4',
                      rinse = False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      reagent_reservoir_volume = 1000,
                      num_wells = 1, #change with num samples
                      delay = 0,
                      h_cono = h_cone,
                      v_fondo = volume_cone  # V cono
                      )

    component5 = Reagent(name = 'component5',
                      rinse = False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      reagent_reservoir_volume = 1000,
                      num_wells = 1, #change with num samples
                      delay = 0,
                      h_cono = h_cone,
                      v_fondo = volume_cone  # V cono
                      )

    component6 = Reagent(name = 'component6',
                      rinse = False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      reagent_reservoir_volume = 1000,
                      num_wells = 1, #change with num samples
                      delay = 0,
                      h_cono = h_cone,
                      v_fondo = volume_cone  # V cono
                      )

    component7 = Reagent(name = 'component7',
                      rinse = False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      reagent_reservoir_volume = 1000,
                      num_wells = 1, #change with num samples
                      delay = 0,
                      h_cono = h_cone,
                      v_fondo = volume_cone  # V cono
                      )

    component8 = Reagent(name = 'component8',
                      rinse = False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      reagent_reservoir_volume = 1000,
                      num_wells = 1, #change with num samples
                      delay = 0,
                      h_cono = h_cone,
                      v_fondo = volume_cone  # V cono
                      )

    MMIX.vol_well = MMIX.vol_well_original
    PC.vol_well = PC.vol_well_original
    NC.vol_well = NC.vol_well_original
    Elution.vol_well = Elution.vol_well_original
    tackpath.vol_well = tackpath.vol_well_original
    covid_assay.vol_well = covid_assay.vol_well_original
    mmix_water.vol_well = mmix_water.vol_well_original
    component4.vol_well = component4.vol_well_original
    component5.vol_well = component5.vol_well_original
    component6.vol_well = component6.vol_well_original
    component7.vol_well = component7.vol_well_original
    component8.vol_well = component8.vol_well_original

################## Assign class type reactives to a summary
    MMIX_components=[tackpath, covid_assay, mmix_water, component4, component5, component6, component7, component8]
    MMIX_components=MMIX_components[:len(MMIX_make[mmix_selection])]
##################

    ##################
    # Custom functions
    def divide_volume(volume,max_vol):
        num_transfers=math.ceil(volume/max_vol)
        vol_roundup=math.ceil(volume/num_transfers)
        last_vol = volume - vol_roundup*(num_transfers-1)
        vol_list = [vol_roundup for v in range(1,num_transfers)]
        vol_list.append(last_vol)
        return vol_list


    def divide_destinations(l, n): # Divide the list of destinations in size n lists.
        a=[]
        for i in range(0, len(l), n):
            a.append( l[i:i + n])
        return a

    def distribute_custom(pipette, reagent, volume, src, dest, waste_pool, pickup_height, extra_dispensal, disp_height=0):
        # Custom distribute function that allows for blow_out in different location and adjustement of touch_tip
        air_gap=10
        pipette.aspirate((len(dest) * volume) +
                         extra_dispensal, src.bottom(pickup_height), rate = reagent.flow_rate_aspirate)
        pipette.touch_tip(speed=20, v_offset=-5)
        pipette.move_to(src.top(z=5))
        pipette.aspirate(air_gap, rate = reagent.flow_rate_aspirate)  # air gap

        for d in dest:
            pipette.dispense(air_gap, d.top(), rate = reagent.flow_rate_dispense)
            drop = d.top(z = disp_height)
            pipette.dispense(volume, drop, rate = reagent.flow_rate_dispense)
            ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent
            pipette.move_to(d.top(z=5))
            pipette.aspirate(air_gap,d.top(z=5), rate = reagent.flow_rate_aspirate)  # air gap

        try:
            pipette.blow_out(waste_pool.wells()[0].bottom(pickup_height + 3))
        except:
            pipette.blow_out(waste_pool.bottom(pickup_height + 3))
        return (len(dest) * volume)

    def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset,
                       pickup_height, rinse, disp_height, blow_out, touch_tip,
                       post_dispense=False, post_dispense_vol=20,
                       post_airgap=False, post_airgap_vol=10):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet, reagent, location = source, vol = vol,
                       rounds = 2, blow_out = True, mix_height = 0,
                       x_offset = x_offset)
        # SOURCE
        s = source.bottom(pickup_height).move(Point(x = x_offset[0]))
        pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol, source.top(z = -2),
                           rate = reagent.flow_rate_aspirate)  # air gap
        # GO TO DESTINATION
        drop = dest.top(z = disp_height).move(Point(x = x_offset[1]))
        pipet.dispense(vol + air_gap_vol, drop,
                       rate = reagent.flow_rate_dispense)  # dispense all
        ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent
        if blow_out == True:
            pipet.blow_out(dest.top(z = -2))
        if post_dispense == True:
            pipet.dispense(post_dispense_vol, dest.top(z = -2))
        if touch_tip == True:
            pipet.touch_tip(speed = 20, v_offset = -5, radius = 0.9)
        if post_airgap == True:
            pipet.aspirate(post_airgap_vol, dest.top(z = 5), rate = 2)



    def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height,
    x_offset, source_height = 3, post_airgap=False, post_airgap_vol=10,
    post_dispense=False, post_dispense_vol=20,touch_tip=False):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height == 0:
            mix_height = 3
        pipet.aspirate(1, location=location.bottom(
            z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate)
        for _ in range(rounds):
            pipet.aspirate(vol, location=location.bottom(
                z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate)
            pipet.dispense(vol, location=location.bottom(
                z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense)
        pipet.dispense(1, location=location.bottom(
            z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out
        if post_dispense == True:
            pipet.dispense(post_dispense_vol, location.top(z = -2))
        if post_airgap == True:
            pipet.dispense(post_airgap_vol, location.top(z = 5))
        if touch_tip == True:
            pipet.touch_tip(speed = 20, v_offset = -5, radius = 0.9)

    def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.3, extra_volume = 30):
        nonlocal ctx
        ctx.comment('Remaining volume ' + str(reagent.vol_well) +
                    '< needed volume ' + str(aspirate_volume) + '?')
        if reagent.vol_well < aspirate_volume + extra_volume:
            reagent.unused.append(reagent.vol_well)
            ctx.comment('Next column should be picked')
            ctx.comment('Previous to change: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('After change: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('New volume:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area
                    #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Remaining volume:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Calculated height is ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('Used height is ' + str(height))
            col_change = False
        return height, col_change

    ############################################
    # tempdeck
    tempdeck = ctx.load_module('tempdeck', '1') #temdeck for qpcr samples
    tempdeck.set_temperature(temperature)

    tempdeck_two = ctx.load_module('tempdeck', '4') #tempdeck for MMIX, PC and NC
    tempdeck_two.set_temperature(temperature)

    ####################################
    # load labware and modules
    # 24 well rack
    tuberack = tempdeck_two.load_labware(
        'opentrons_24_aluminumblock_generic_2ml_screwcap',
        'Bloque Aluminio opentrons 24 screwcaps 2000 µL ')

    ##################################
    # qPCR plate - final plate, goes to PCR
    qpcr_plate = tempdeck.load_labware(
        'abi_fast_qpcr_96_alum_opentrons_100ul',
        'chilled qPCR final plate')

    ##################################
    # waste reservoir
    waste_reservoir = ctx.load_labware(
        'nest_1_reservoir_195ml', '9', 'waste reservoir')
    waste = waste_reservoir.wells()[0]  # referenced as reservoir

    ##################################
    # Sample plate - comes from B
    source_plate = ctx.load_labware(
        "kingfisher_std_96_wellplate_550ul", '2',
        'chilled KF plate with elutions (alum opentrons)')
    samples = source_plate.wells()[:NUM_SAMPLES]

    ##################################
    # Load Tipracks
    tips20 = [
        ctx.load_labware('opentrons_96_filtertiprack_20ul', slot)
        for slot in ['5']
    ]

    tips200 = [
        ctx.load_labware('opentrons_96_filtertiprack_200ul', slot)
        for slot in ['6']
    ]

    # pipettes
    m20 = ctx.load_instrument(
        'p20_multi_gen2', mount='left', tip_racks=tips20)

    if STEPS[2]['Execute']==True:
        p300 = ctx.load_instrument(
        'p300_single_gen2', mount='right', tip_racks=tips200)
        # used tips counter
        tip_track = {
            'counts': {p300: 0, m20: 0},
            'maxes': {p300: len(tips200) * 96, m20: len(tips20)*96}
        }
    else:
        tips20mmix = [ ctx.load_labware('opentrons_96_filtertiprack_20ul', slot)
                        for slot in ['8'] ]
        p20 = ctx.load_instrument(
        'p20_single_gen2', mount='right', tip_racks=tips20mmix)
        # used tips counter
        tip_track = {
            'counts': {p20: 0, m20: 0},
            'maxes': {p20: len(tips200) * 96, m20: len(tips20)*96}
        }



    ################################################################################
    # Declare which reagents are in each reservoir as well as deepwell and elution plate
    MMIX.reagent_reservoir = tuberack.rows()[0][:MMIX.num_wells] # 1 row, 2 columns (first ones)
    MMIX_components_location=tuberack.wells()[MMIX_make_location:(MMIX_make_location + len(MMIX_make[mmix_selection]))]
    ctx.comment('Wells in: '+ str(tuberack.rows()[0][:MMIX.num_wells]) + ' element: '+str(MMIX.reagent_reservoir[MMIX.col]))
    PC.reagent_reservoir = tuberack.rows()[1][:1]
    NC.reagent_reservoir = tuberack.rows()[2][:1]
    # setup up sample sources and destinations
    samples = source_plate.wells()[:NUM_SAMPLES]
    samples_multi = source_plate.rows()[0][:num_cols]
    pcr_wells = qpcr_plate.wells()[:(NUM_SAMPLES)]
    pcr_wells_multi = qpcr_plate.rows()[0][:num_cols]
    pc_well_old = source_plate.wells()[(NUM_SAMPLES-2):(NUM_SAMPLES-1)][0]
    nc_well_old = source_plate.wells()[(NUM_SAMPLES-1):(NUM_SAMPLES)][0]
    pc_well = qpcr_plate.wells()[(NUM_SAMPLES-2):(NUM_SAMPLES-1)][0]
    nc_well = qpcr_plate.wells()[(NUM_SAMPLES-1):(NUM_SAMPLES)][0]
    # Divide destination wells in small groups for P300 pipette
    dests = list(divide_destinations(pcr_wells, size_transfer))

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        if not ctx.is_simulating():
            if tip_track['counts'][pip] == tip_track['maxes'][pip]:
                ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \
                resuming.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0

        if not pip.hw_pipette['has_tip']:
            pip.pick_up_tip()
    ##########
    ############################################################################
    # STEP 1: Make Master MIX
    ############################################################################
    ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        # Check if among the pipettes, p300_single is installed
        used_vol=[]
        ctx.comment('#######################################################')
        ctx.comment('Selected MMIX: '+MMIX_available[mmix_selection])
        ctx.comment('#######################################################')

        for i,[source, vol] in enumerate(zip(MMIX_components_location, MMIX_make[mmix_selection])):
            pick_up(p300)
            ctx.comment('#######################################################')
            ctx.comment('Add component: '+MMIX_components[i].name)
            ctx.comment('#######################################################')
            if (vol + air_gap_vol) > pipette_allowed_capacity: # because 200ul is the maximum volume of the tip we will choose 180
            # calculate what volume should be transferred in each step
                vol_list=divide_volume(vol, pipette_allowed_capacity)
                for vol in vol_list:
                    move_vol_multichannel(p300, reagent=MMIX_components[i], source=source, dest=MMIX.reagent_reservoir[0],
                    vol=vol, air_gap_vol=air_gap_vol, x_offset = x_offset,pickup_height=1, # should be changed with picku_up_height calculation
                    rinse=False, disp_height=-10,blow_out=True, touch_tip=True)
            else:
                move_vol_multichannel(p300, reagent=MMIX_components[i], source=source, dest=MMIX.reagent_reservoir[0],
                vol=vol, air_gap_vol=air_gap_vol, x_offset=x_offset, pickup_height=1, # should be changed with picku_up_height calculation
                rinse=False, disp_height=-10,blow_out=True, touch_tip=True)

            if i+1<len(MMIX_components):
                p300.drop_tip()
            else:
                ctx.comment('#######################################################')
                ctx.comment('Final mix')
                ctx.comment('#######################################################')
                custom_mix(p300, reagent = MMIX, location = MMIX.reagent_reservoir[0], vol = 180, rounds = 5,
                blow_out = True, mix_height = 2, x_offset = x_offset)
                p300.drop_tip()

            tip_track['counts'][p300]+=1

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('#######################################################')
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        ctx.comment('#######################################################')
        STEPS[STEP]['Time:'] = str(time_taken)


    ############################################################################
    # STEP 2: Transfer Master MIX with P300
    ############################################################################
    ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        p300.pick_up_tip()

        for dest in pcr_wells:
            [pickup_height, col_change] = calc_height(MMIX, area_section_screwcap, volume_mmix)

            move_vol_multichannel(p300, reagent = MMIX, source = MMIX.reagent_reservoir[MMIX.col],
            dest = dest, vol = volume_mmix, air_gap_vol = air_gap_mmix, x_offset = x_offset,
                   pickup_height = pickup_height, disp_height = -10, rinse = False,
                   blow_out=True, touch_tip=False)

            #used_vol.append(used_vol_temp)

        p300.drop_tip()
        tip_track['counts'][p300]+=1
        #MMIX.unused_two = MMIX.vol_well

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('#######################################################')
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        ctx.comment('#######################################################')
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 3: Transfer Master MIX with P20
    ############################################################################
    ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        p20.pick_up_tip()

        for dest in pcr_wells:
            [pickup_height, col_change] = calc_height(MMIX, area_section_screwcap, volume_mmix)

            move_vol_multichannel(p20, reagent = MMIX, source = MMIX.reagent_reservoir[MMIX.col],
            dest = dest, vol = volume_mmix, air_gap_vol = 0, x_offset = x_offset,
                   pickup_height = pickup_height, disp_height = -10, rinse = False,
                   blow_out=True, touch_tip=True)

            #used_vol.append(used_vol_temp)

        p20.drop_tip()
        tip_track['counts'][p20]+=1
        #MMIX.unused_two = MMIX.vol_well

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('#######################################################')
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        ctx.comment('#######################################################')
        STEPS[STEP]['Time:'] = str(time_taken)
        ctx.pause('Put samples please')

    ############################################################################
    # STEP 3: Clean up PC and NC well
    ############################################################################
    ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        clean_up_wells=[pc_well_old,nc_well_old]
        p20.pick_up_tip()
        for src in clean_up_wells:
            for i in range(3):

                move_vol_multichannel(p20, reagent = PC, source = src,
                dest = waste, vol = 20, air_gap_vol = 0, x_offset = [0,0],
                       pickup_height = 0.2, disp_height = 5, rinse = False,
                       blow_out=True, touch_tip=False,post_airgap=False, post_airgap_vol=0)

        p20.drop_tip()
        tip_track['counts'][p20]+=1
        #MMIX.unused_two = MMIX.vol_well

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('#######################################################')
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        ctx.comment('#######################################################')
        STEPS[STEP]['Time:'] = str(time_taken)


    ############################################################################
    # STEP 4: TRANSFER Samples
    ############################################################################
    ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment('pcr_wells')
        #Loop over defined wells
        for s, d in zip(samples_multi, pcr_wells_multi):
            m20.pick_up_tip()
            #Source samples
            move_vol_multichannel(m20, reagent = Elution, source = s, dest = d,
            vol = volume_sample, air_gap_vol = air_gap_sample, x_offset = x_offset,
                   pickup_height = 0.3, disp_height = -10, rinse = False,
                   blow_out=True, touch_tip=False, post_airgap=False)
            # Mixing
            custom_mix(m20, Elution, d, vol=5, rounds=2, blow_out=True,
                        mix_height=6, post_dispense=True, source_height=0.5, x_offset=[0,0],touch_tip=True)

            m20.drop_tip()
            tip_track['counts'][m20]+=8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('#######################################################')
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        ctx.comment('#######################################################')
        STEPS[STEP]['Time:'] = str(time_taken)



    ############################################################################
    # STEP 5: Transfer PC with P20
    ############################################################################
    ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        p20.pick_up_tip()

        #[pickup_height, col_change] = calc_height(PC, area_section_screwcap, volume_mmix)

        move_vol_multichannel(p20, reagent = PC, source = PC.reagent_reservoir[PC.col],
        dest = pc_well, vol = volume_pc, air_gap_vol = 0, x_offset = x_offset,
               pickup_height = 0.2, disp_height = -10, rinse = False,
               blow_out=True, touch_tip=True)

        p20.drop_tip(home_after=False)
        tip_track['counts'][p20]+=1

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('#######################################################')
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        ctx.comment('#######################################################')
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 6: Transfer NC with P20
    ############################################################################
    ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        p20.pick_up_tip()

        move_vol_multichannel(p20, reagent = NC, source = NC.reagent_reservoir[NC.col],
        dest = nc_well, vol = volume_nc, air_gap_vol = 0, x_offset = x_offset,
               pickup_height = 0.2, disp_height = -10, rinse = False,
               blow_out=True, touch_tip=True)

        p20.drop_tip(home_after=False)
        tip_track['counts'][p20]+=1
        #MMIX.unused_two = MMIX.vol_well

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('#######################################################')
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        ctx.comment('#######################################################')
        STEPS[STEP]['Time:'] = str(time_taken)
    tempdeck.deactivate()
    tempdeck_two.deactivate()


    ############################################################################
    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write('STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    # Light flash end of program
    ctx.home()
    time.sleep(2)
    import os
    #os.system('mpg123 -f -8000 /etc/audio/speaker-test.mp3 &')

    '''if STEPS[1]['Execute'] == True:
        total_used_vol = np.sum(used_vol)
        total_needed_volume = total_used_vol
        ctx.comment('Total Master Mix used volume is: ' + str(total_used_vol) + '\u03BCl.')
        ctx.comment('Needed Master Mix volume is ' +
                    str(total_needed_volume + extra_dispensal*len(dests)) +'\u03BCl')
        ctx.comment('Used Master Mix volumes per run are: ' + str(used_vol) + '\u03BCl.')
        ctx.comment('Master Mix Volume remaining in tubes is: ' +
                    format(np.sum(MMIX.unused)+extra_dispensal*len(dests)+MMIX.vol_well) + '\u03BCl.')
        ctx.comment('200 ul Used tips in total: ' + str(tip_track['counts'][p300]))
        ctx.comment('200 ul Used racks in total: ' + str(tip_track['counts'][p300] / 96))'''

    if (STEPS[3]['Execute'] == True & STEPS[4]['Execute'] == True):
        ctx.comment('20 ul Used tips in total: ' + str(tip_track['counts'][m20]+tip_track['counts'][p20]))
        ctx.comment('20 ul Used racks in total: ' + str((tip_track['counts'][m20]+tip_track['counts'][p20]) / 96))

    if (STEPS[2]['Execute'] == True & STEPS[4]['Execute'] == True):
        ctx.comment('200 ul Used tips in total: ' + str(tip_track['counts'][p300]))
        ctx.comment('200 ul Used racks in total: ' + str(tip_track['counts'][p300] / 96))
        ctx.comment('20 ul Used tips in total: ' + str(tip_track['counts'][m20]))
        ctx.comment('20 ul Used racks in total: ' + str((tip_track['counts'][m20] / 96)))

    if (STEPS[2]['Execute'] == True & STEPS[5]['Execute'] == False):
        ctx.comment('200 ul Used tips in total: ' + str(tip_track['counts'][p300]))
        ctx.comment('200 ul Used racks in total: ' + str(tip_track['counts'][p300] / 96))

    if (STEPS[3]['Execute'] == False & STEPS[4]['Execute'] == True):
        ctx.comment('20 ul Used tips in total: ' + str(tip_track['counts'][m20]))
        ctx.comment('20 ul Used racks in total: ' + str((tip_track['counts'][m20] / 96)))

    ctx.comment('Finished! \nMove plate to PCR')
Example #13
0
def run(ctx: protocol_api.ProtocolContext):
    [
        _m300_mount, _num_samps, _inc_time, _mag_time, _air_dry, _add_water,
        _samp_labware, _asp_ht, _fin_wash, _elution_vol
    ] = get_values(  # noqa: F821 (<--- DO NOT REMOVE!)
        '_m300_mount', '_num_samps', '_inc_time', '_mag_time', '_air_dry',
        '_add_water', '_samp_labware', '_asp_ht', '_fin_wash', '_elution_vol')

    if not 1 <= _num_samps <= 96:
        raise Exception("The 'Number of Samples' should be between 1 and 96")

    # define all custom variables above here with descriptions

    m300_mount = _m300_mount  # mount for 8-channel p300 pipette
    num_cols = math.ceil(_num_samps / 8)  # number of sample columns
    samp_labware = _samp_labware  # labware containing sample
    elution_vol = _elution_vol  # volume of elution buffer
    inc_time = _inc_time
    mag_time = _mag_time
    air_dry = _air_dry
    fin_wash = _fin_wash
    asp_ht = _asp_ht
    add_water = _add_water

    mag_deck = ctx.load_module('magnetic module gen2', 7)

    # load labware
    rsvr_12 = [ctx.load_labware('nest_12_reservoir_15ml', s) for s in [2, 3]]
    rsvr_1 = ctx.load_labware('nest_1_reservoir_195ml', 10)
    pcr_plate = ctx.load_labware('nest_96_wellplate_100ul_pcr_full_skirt', 1)
    samp_plate = ctx.load_labware(samp_labware, 4)
    mag_plate = mag_deck.load_labware('nest_96_wellplate_2ml_deep')

    # load tipracks
    tips = [
        ctx.load_labware('opentrons_96_filtertiprack_200ul', s)
        for s in [5, 6, 8, 9, 11]
    ]
    all_tips = [t for rack in tips for t in rack.rows()[0]]
    t_start = 0
    t_end = int(num_cols)

    # load instrument
    m300 = ctx.load_instrument('p300_multi_gen2', m300_mount, tip_racks=tips)

    # extend well objects for improved liquid handling
    class WellH(Well):
        def __init__(self,
                     well,
                     min_height=0.5,
                     comp_coeff=1.1,
                     current_volume=0):
            super().__init__(well._impl)
            self.well = well
            # specified minimum well bottom clearance
            self.min_height = min_height
            self.comp_coeff = comp_coeff
            # specified starting volume in ul
            self.current_volume = current_volume
            # cross sectional area
            if self.diameter is not None:
                self.radius = self.diameter / 2
                cse = math.pi * (self.radius**2)
            elif self.length is not None:
                cse = self.length * self.width
            else:
                cse = None
            self.cse = cse
            # initial liquid level in mm from start vol
            if cse:
                self.height = (current_volume / cse)
            else:
                raise Exception("""Labware definition must
                supply well radius or well length and width.""")
            if self.height < min_height:
                self.height = min_height
            elif self.height > well.parent.highest_z:
                raise Exception("""Specified liquid volume
                can not exceed the height of the labware.""")

        def height_dec(self, vol, ppt, bottom=False):
            # decrement height (mm)
            dh = (vol / self.cse) * self.comp_coeff
            # tip immersion (mm) as fraction of tip length
            mm_immersed = 0.05 * ppt._tip_racks[0].wells()[0].depth
            # decrement til target reaches specified min clearance
            self.height = self.height - dh if (
                (self.height - dh - mm_immersed) > self.min_height
            ) else self.min_height + mm_immersed
            self.current_volume = self.current_volume - vol if (
                self.current_volume - vol > 0) else 0
            tip_ht = self.height - mm_immersed if bottom is False else bottom
            return (self.well.bottom(tip_ht))

        def height_inc(self, vol, top=False):
            # increment height (mm)
            ih = (vol / self.cse) * self.comp_coeff
            # keep calculated liquid ht between min clearance and well depth
            self.height = self.min_height if (
                self.height < self.min_height) else self.height
            self.height = (self.height + ih) if (
                (self.height + ih) < self.depth) else self.depth
            # increment
            self.current_volume += vol
            if top is False:
                tip_ht = self.height
                return (self.well.bottom(tip_ht))
            else:
                return (self.well.top())

    # pipette functions   # INCLUDE ANY BINDING TO CLASS
    def aspirate_h(self, vol, source, rate=1, bottom=False):
        self.aspirate(vol,
                      source.height_dec(vol, self, bottom=bottom),
                      rate=rate)

    def dispense_h(self, vol, dest, rate=1, top=False):
        self.dispense(vol, dest.height_inc(vol, top=top), rate=rate)

    def slow_tip_withdrawal(self,
                            speed_limit,
                            well_location,
                            to_surface=False):
        if self.mount == 'right':
            axis = 'A'
        else:
            axis = 'Z'
        previous_limit = None
        if axis in ctx.max_speeds.keys():
            for key, value in ctx.max_speeds.items():
                if key == axis:
                    previous_limit = value
        ctx.max_speeds[axis] = speed_limit
        if to_surface is False:
            self.move_to(well_location.top())
        else:
            if isinstance(well_location, WellH):
                self.move_to(well_location.bottom().move(
                    types.Point(x=0,
                                y=0,
                                z=well_location.height +
                                (20 *
                                 (self._tip_racks[0].wells()[0].depth / 88)))))
            else:
                self.move_to(well_location.center())
        ctx.max_speeds[axis] = previous_limit

    def custom_pick_up(self, loc=None):
        nonlocal t_start
        nonlocal t_end
        """`custom_pick_up` will pause the protocol when all tip boxes are out of
        tips, prompting the user to replace all tip racks. Once tipracks are
        reset, the protocol will start picking up tips from the first tip
        box as defined in the slot order when assigning the labware definition
        for that tip box. `pick_up()` will track tips for both pipettes if
        applicable.

        :param loc: User can manually specify location for tip pick up
        """
        if loc:
            self.pick_up_tip(loc)
        else:
            try:
                self.pick_up_tip()
            except protocol_api.labware.OutOfTipsError:
                flash_lights()
                ctx.pause("Replace empty tip racks")
                self.reset_tipracks()
                t_start = 0
                t_end = int(num_cols)
                ctx.set_rail_lights(True)
                self.pick_up_tip()

    # bind additional methods to pipettes
    for met in [aspirate_h, dispense_h, slow_tip_withdrawal, custom_pick_up]:
        setattr(m300, met.__name__, MethodType(met, m300))

    # reagents
    liquid_waste = rsvr_1.wells()[0].top()
    cspl = [WellH(well) for well in rsvr_12[0].wells()[:6]]
    for idx in range(num_cols):
        cspl[idx // 2].height_inc(720 * 8 * 1.1)

    # helper functions
    def flash_lights():
        for _ in range(19):
            ctx.set_rail_lights(not ctx.rail_lights_on)
            ctx.delay(seconds=0.25)

    def flow_rate(asp=92.86, disp=92.86, blow=92.86):
        """
        This function can be used to quickly modify the flow rates of the m300
        If no parameters are entered, the flow rates will be
        reset.

        :param asp: Aspiration flow rate, in uL/sec
        :param disp: Dispense flow rate, in uL/sec
        """
        m300.flow_rate.aspirate = asp
        m300.flow_rate.dispense = disp
        m300.flow_rate.blow_out = blow

    def remove_supernatant(vol, src):
        w = int(str(src).split(' ')[0][1:])
        radi = float(src.width)/4 if src.width is not None else \
            float(src.diameter)/4
        x0 = radi if w % 2 == 0 else -radi
        while vol > 180:
            m300.aspirate(180, src.bottom().move(types.Point(x=x0, y=0, z=1)))
            m300.dispense(200, liquid_waste)
            m300.blow_out()
            m300.aspirate(20, liquid_waste)
            vol -= 180
        m300.aspirate(vol, src.bottom().move(types.Point(x=x0, y=0, z=0.7)))
        m300.dispense(vol + 20, liquid_waste)
        m300.blow_out()
        m300.aspirate(10, liquid_waste)

    def wash(srcs, msg, sup=540):
        nonlocal t_start
        nonlocal t_end

        if mag_deck.status == 'engaged':
            mag_deck.disengage()
        ctx.comment(f'\nPerforming wash step: {msg}\n')
        flow_rate()
        for idx, col in enumerate(mag_samps_h):
            m300.custom_pick_up()
            src = srcs[idx // 3]
            for i in range(2):
                flow_rate(asp=150, disp=150)
                if i == 1:
                    m300.dispense(20, src.top(-1))
                    m300.dispense(20, src)
                m300.mix(2, 200, src)
                flow_rate()
                m300.aspirate(200, src)
                m300.slow_tip_withdrawal(10, src, to_surface=True)
                m300.dispense(180, col.top(-2))
                flow_rate(asp=10)
                m300.aspirate(20, col.top())
            m300.dispense(20, src.top())
            flow_rate()
            m300.mix(1, 140, src)
            m300.aspirate(140, src)
            m300.slow_tip_withdrawal(10, src, to_surface=True)
            m300.dispense(140, col)
            m300.mix(10, 100, col)
            ctx.delay(seconds=5)
            m300.slow_tip_withdrawal(10, col, to_surface=True)
            m300.blow_out()
            m300.touch_tip(speed=40)
            m300.aspirate(10, col.top())
            m300.drop_tip()

        mag_deck.engage()
        mag_msg = f'\nIncubating on Mag Deck for {mag_time} minutes\n'
        ctx.delay(minutes=mag_time, msg=mag_msg)

        # Discard Supernatant
        ctx.comment(f'\nRemoving supernatant for wash: {msg}\n')
        t_start += num_cols
        t_end += num_cols
        for src, t_d in zip(mag_samps_h, all_tips[t_start:t_end]):
            m300.custom_pick_up()
            remove_supernatant(sup, src)
            m300.drop_tip(t_d)

    # plate, tube rack maps
    init_samps = samp_plate.rows()[0][:num_cols]
    mag_samps = mag_plate.rows()[0][:num_cols]
    mag_samps_h = [WellH(well) for well in mag_samps]
    pcr_samps = pcr_plate.rows()[0][:num_cols]

    # protocol
    ctx.set_rail_lights(True)
    # # Transfer 700µL CSPL Buffer + 20µL Prot K
    # ctx.comment('\nTransferring 720uL of CSPL Buffer + Proteinase K\n')
    #
    # m300.custom_pick_up()
    # for idx, col in enumerate(init_samps):
    #     src = cspl[idx//2]
    #     for _ in range(4):
    #         m300.aspirate_h(180, src)
    #         m300.slow_tip_withdrawal(10, src, to_surface=True)
    #         m300.dispense(180, col.top(-2))
    # m300.drop_tip()
    #
    # flash_lights()
    # ctx.pause('Please remove samples and incubate at 56C for 30 minutes, \
    # then centrifuge at 4000g for 10 minutes. Once complete, please replace \
    # samples on the deck and place ensure 12-well reservoirs are filled with \
    # necessary reagents in deck slots 2 and 3. When ready, click RESUME.')
    # ctx.set_rail_lights(True)
    # Creating reagent variables for second part of protocol
    rbb = [WellH(well, current_volume=0) for well in rsvr_12[0].wells()[:4]]
    cspw1 = [WellH(well) for well in rsvr_12[0].wells()[6:10]]
    cspw2 = [WellH(well) for well in rsvr_12[1].wells()[:4]]
    spm1 = [WellH(well) for well in rsvr_12[1].wells()[4:8]]
    spm2 = [WellH(well) for well in rsvr_12[1].wells()[8:]]
    elution_buffer = [WellH(well) for well in rsvr_12[0].wells()[10:]]

    for idx in range(num_cols):
        rbb[idx // 3].height_inc(525 * 8)
        cspw1[idx // 3].height_inc(500 * 8)
        cspw2[idx // 3].height_inc(500 * 8)
        spm1[idx // 3].height_inc(500 * 8)
        spm2[idx // 3].height_inc(500 * 8)
        cspw1[idx // 6].height_inc(elution_vol * 8)

    ctx.comment('\nTransferring 500uL of sample to plate on MagDeck\n')

    flow_rate(asp=20)
    for src, dest in zip(init_samps, mag_samps_h):
        if samp_labware == 'qiagen_96_tuberack_1200ul':
            src_asp = src.top(-asp_ht)
        else:
            src_asp = src
        m300.custom_pick_up()
        for i in range(2):
            m300.aspirate(20, src.top())
            m300.aspirate(180, src_asp)
            m300.slow_tip_withdrawal(10, src)
            m300.dispense_h(180, dest)
            m300.slow_tip_withdrawal(10, dest, to_surface=True)
            m300.dispense(20, dest.bottom(5))
        m300.aspirate(20, src.top())
        m300.aspirate(140, src_asp)
        m300.dispense_h(140, dest)
        m300.slow_tip_withdrawal(10, dest, to_surface=True)
        m300.dispense(20, dest.bottom(5))
        m300.drop_tip()
    flow_rate()

    # Transfer 5uL RNAse + 500uL RBB buffer + 20uL Mag-Bind Beads
    ctx.comment('\nTransferring 5uL RNAse + 500uL RBB buffer + \
    20uL Mag-Bind Beads\n')

    m300.custom_pick_up()
    flow_rate(blow=10)
    for idx, col in enumerate(mag_samps):
        src = rbb[idx // 2]
        for _ in range(3):
            flow_rate(asp=20, disp=20)
            m300.mix(1, 100, src)
            m300.aspirate(175, src)
            m300.slow_tip_withdrawal(10, src, to_surface=True)
            flow_rate(disp=10)
            m300.dispense(75, dest.top(-1))
            flow_rate(disp=5)
            m300.dispense(50, dest.top(-1))
            ctx.delay(seconds=3)
            m300.dispense(30, dest.top(-1))
            ctx.delay(seconds=3)
            flow_rate(disp=2)
            m300.dispense(20, dest.top(-1))
            m300.blow_out()
            for x in range(2):
                m300.move_to(dest.top(-2))
                m300.move_to(dest.top(-1))
    m300.drop_tip()
    flow_rate()

    incubate_msg = f'\nIncubating at room temperature for {inc_time} minutes\
     plus mixing\n'

    ctx.comment(incubate_msg)

    num_mixes = math.ceil(2 * inc_time / num_cols)
    for _ in range(num_mixes):
        for col, t_d in zip(mag_samps, all_tips[t_start:t_end]):
            if _ == 0:
                m300.custom_pick_up()
            if not m300.has_tip:
                m300.custom_pick_up(t_d)
            m300.aspirate(20, col.top())
            m300.mix(8, 150, col)
            ctx.delay(seconds=1)
            m300.dispense(20, col.top(-2))
            m300.blow_out(col.top(-2))
            m300.touch_tip()
            if len(mag_samps) > 1:
                m300.drop_tip(t_d)
    if m300.has_tip:
        m300.drop_tip(t_d)

    t_start += num_cols
    t_end += num_cols
    mag_deck.engage()
    mag_msg = f'\nIncubating on Mag Deck for {mag_time} minutes\n'
    ctx.delay(minutes=mag_time, msg=mag_msg)

    # Discard Supernatant
    ctx.comment('\nRemoving supernatant\n')
    for src, t_d in zip(mag_samps_h, all_tips[t_start:t_end]):
        m300.custom_pick_up()
        flow_rate(asp=40, disp=40)
        remove_supernatant(1080, src)
        m300.drop_tip(t_d)

    flow_rate()

    # Wash with 500uL CSPW1 Buffer
    wash(cspw1, 'CSPW1')

    # Wash with 500uL CSPW2 Buffer
    wash(cspw2, 'CSPW2')

    # Wash with SPM Buffer (1)
    wash(spm1, 'SPM (first wash)')

    # Wash with SPM Buffer (2)
    wash(spm2, 'SPM (second wash)', fin_wash)

    # Air dry for 10 minutes
    mag_deck.engage()
    if add_water:
        h2o = rsvr_12[0]['A4']
        for src in mag_samps_h:
            m300.custom_pick_up()
            m300.aspirate(100, h2o)
            flow_rate(asp=30, disp=30)
            m300.dispense(80, src)
            m300.aspirate(100, src)
            flow_rate()
            m300.dispense(180, liquid_waste)
            m300.blow_out()
            m300.drop_tip()

    ctx.home()
    air_dry_msg = f'\nAir drying the beads for {air_dry} minutes. \
    Please add elution buffer at 65C to 12-well reservoir.\n'

    ctx.delay(minutes=air_dry, msg=air_dry_msg)
    flash_lights()
    if not ctx.is_simulating():
        test_speaker()
    ctx.pause('Please check the Well Plate')

    mag_deck.disengage()
    # Add Elution Buffer
    ctx.comment(f'\nAdding {elution_vol}uL Elution Buffer to samples\n')

    for idx, col in enumerate(mag_samps_h):
        src = elution_buffer[idx // 6]
        m300.custom_pick_up()
        m300.aspirate(elution_vol, src)
        m300.slow_tip_withdrawal(10, src, to_surface=True)
        m300.dispense_h(elution_vol, col)
        m300.mix(10, 50, col)
        m300.slow_tip_withdrawal(10, col, to_surface=True)
        m300.blow_out(col.bottom(6))
        for _ in range(2):
            m300.move_to(col.bottom(5))
            m300.move_to(col.bottom(6))
        m300.drop_tip()

    flash_lights()
    ctx.home()
    ctx.pause('Please remove samples and incubate at 65C for 5 minutes.\
    When complete, replace samples and click RESUME\n')
    ctx.set_rail_lights(True)

    # Transfer elution to PCR plate
    for col in mag_samps_h:
        m300.custom_pick_up()
        m300.mix(10, 50, col)
        m300.slow_tip_withdrawal(10, col, to_surface=True)
        m300.blow_out(col.bottom(6))
        for _ in range(2):
            m300.move_to(col.bottom(5))
            m300.move_to(col.bottom(6))
        m300.drop_tip(home_after=False)

    mag_deck.engage()
    mag_msg = '\nIncubating on Mag Deck for 3 minutes\n'
    ctx.delay(minutes=3, msg=mag_msg)

    ctx.comment(f'\nTransferring {elution_vol}uL to final PCR plate\n')
    t_start += num_cols
    if t_start >= 60:
        t_start -= 60

    flow_rate(asp=20)
    for src, dest, tip in zip(mag_samps, pcr_samps, all_tips[t_start:]):
        w = int(str(src).split(' ')[0][1:])
        radi = float(src.width)/4 if src.width is not None else \
            float(src.diameter)/4
        x0 = radi if w % 2 == 0 else -radi
        m300.custom_pick_up()
        m300.aspirate(elution_vol,
                      src.bottom().move(types.Point(x=x0, y=0, z=1)))
        m300.dispense(elution_vol, dest)
        m300.drop_tip(tip)

    mag_deck.disengage()
    ctx.comment('\nProtocol complete! Please store samples at -20C or \
    continue processing')
def run(protocol: protocol_api.ProtocolContext):
    #Lights prep
    gpio = protocol._hw_manager.hardware._backend._gpio_chardev

    #labware
    rack1 = protocol.load_labware(
        'nest_32_tuberack_8x15ml_8x15ml_8x15ml_8x15ml', '1')
    # rack2 = protocol.load_labware('nest_32_tuberack_8x15ml_8x15ml_8x15ml_8x15ml','4')
    # rack3 = protocol.load_labware('nest_32_tuberack_8x15ml_8x15ml_8x15ml_8x15ml','7')
    tuberacks = [rack1]
    tiprack_1000_1 = protocol.load_labware('opentrons_96_tiprack_1000ul', '10')
    empty_tiprack_1000 = protocol.load_labware(
        'opentrons_96_filtertiprack_1000ul', '11')
    deepwell_96 = protocol.load_labware('nest_96_wellplate_2ml_deep', '3')

    #Pipettes
    r_p = protocol.load_instrument('p1000_single_gen2',
                                   'right',
                                   tip_racks=[tiprack_1000_1])

    # Mapping of Wells
    sources = [
        well for rack in tuberacks[::-1] for row in rack.rows()[:0:-1]
        for well in row[::-1]
    ]
    destination_sets = [
        row[i * 5:i * 5 + 5] for i in range(3)
        for row in deepwell_96.rows()[::-1]
    ]

    #Pipette max_speeds
    r_p.flow_rate.aspirate = 400
    r_p.flow_rate.dispense = 400
    r_p.flow_rate.blow_out = 400

    tip_droptip_count = 0
    tip_pickup_count = 0

    #Light turns to yellow to indicate protocol is running
    gpio.set_button_light(red=True, green=True, blue=False)

    #Pipetting Actions
    for source, dest_set in zip(sources, destination_sets):
        r_p.pick_up_tip(tiprack_1000_1.wells()[tip_pickup_count])

        #dispense round 1
        r_p.aspirate(550, source.top(-92))
        r_p.air_gap(20)

        for i, dest in enumerate(
                dest_set):  # go through the entries for each tube
            disp_vol = 10 + 105 if i == 0 else 105
            tip_pickup_count += 1

            r_p.dispense(disp_vol, dest.bottom(5))
            r_p.touch_tip(v_offset=-6, speed=40)

        #dispense round 2
        r_p.aspirate(500, source.top(-92))
        r_p.air_gap(20)

        for i, dest in enumerate(
                dest_set):  # go through the entries for each tube
            disp_vol = 10 + 105 if i == 0 else 105

            r_p.dispense(disp_vol, dest.bottom(5))
            r_p.touch_tip(v_offset=-6, speed=40)

        #Home the Z/A mount. Not the pipette
        r_p.air_gap(
            100)  # aspirate any liquid that may be leftover inside the tip
        protocol._implementation.get_hardware().hardware.home_z(
            r_p._implementation.get_mount())
        current_location = protocol._implementation.get_hardware(
        ).hardware.gantry_position(r_p._implementation.get_mount())
        final_location_xy = current_location._replace(y=300, x=300)
        r_p.move_to(Location(labware=None, point=final_location_xy),
                    force_direct=True)

        r_p.drop_tip(empty_tiprack_1000.wells()[tip_droptip_count])
        tip_droptip_count += 1

    gpio.set_button_light(red=False, green=True, blue=False)
    protocol.pause()
    protocol.home()
    gpio.set_button_light(red=False, green=False, blue=True)
def run(protocol: protocol_api.ProtocolContext):

    # labware: Labware used in the protocol is loaded, format: variable = labware.load('labware name', 'Position on deck')
    plate_dil_1 = protocol.load_labware('nunc_96_ubottom', '4')
    plate_vt_1 = protocol.load_labware('valitacell_96_wellplate_150ul', '1')
    plate_dil_2 = protocol.load_labware('nunc_96_ubottom', '5')
    plate_vt_2 = protocol.load_labware('valitacell_96_wellplate_150ul', '2')
    plate_dil_3 = protocol.load_labware('nunc_96_ubottom', '6')
    plate_vt_3 = protocol.load_labware('valitacell_96_wellplate_150ul', '3')
    tip200_1 = protocol.load_labware('opentrons_96_filtertiprack_200ul', '7')
    tip200_2 = protocol.load_labware('opentrons_96_filtertiprack_200ul', '10')
    tip200_3 = protocol.load_labware('opentrons_96_filtertiprack_200ul', '11')
    tip200_4 = protocol.load_labware('opentrons_96_filtertiprack_200ul', '9')
    trough = protocol.load_labware('axygen_12_reservior_22ml', '8')

    # pipettes and settings
    p300m = protocol.load_instrument(
        'p300_multi',
        mount='right',
        tip_racks=[tip200_1, tip200_2, tip200_3, tip200_4])
    p300m.flow_rate.aspirate = 100
    p300m.flow_rate.dispense = 200
    p300m.maximum_volume = 200
    p300m.minimum_volume = 15

    def VT_SC_Plate(trough_well, plate_dil,
                    plate_vt):  #FDefines running of VT_SC_Plate as a function
        #Step1: Fill Dilution media to cols 2-12
        p300m.pick_up_tip()
        for i in range(11):
            p300m.aspirate(100, trough.wells()[trough_well])
            p300m.move_to(trough.wells()[trough_well].top(-20))
            protocol.delay(seconds=1)
            p300m.dispense(100, plate_dil.wells()[8 * (i + 1)])
            protocol.delay(seconds=1)
            p300m.blow_out()
        p300m.drop_tip()

        #Step2: Add 222ul Standard to dil_plate Col-1
        p300m.pick_up_tip()
        p300m.mix(10, 190, trough.wells()[0])
        protocol.delay(seconds=1)
        p300m.blow_out(trough.wells()[0].top())
        p300m.transfer(100,
                       trough.wells()[0],
                       plate_dil.wells()[0],
                       new_tip='never')
        protocol.delay(seconds=1)
        p300m.blow_out()
        p300m.transfer(122,
                       trough.wells()[0],
                       plate_dil.wells()[0],
                       new_tip='never')
        protocol.delay(seconds=1)
        p300m.blow_out()
        p300m.drop_tip()

        #Step3: Dilute at ration of 0.6 accross cols 1-11
        p300m.pick_up_tip()
        for i in range(10):
            p300m.transfer(122,
                           plate_dil.wells()[8 * i],
                           plate_dil.wells()[8 * (i + 1)],
                           mix_after=(6, 180),
                           new_tip='never')
            protocol.delay(seconds=1)
            p300m.blow_out(plate_dil.wells()[8 * (i + 1)].top())
        p300m.drop_tip()
        protocol.home()

        #Step3: Add 60ul VT-Buff to VT plate
        p300m.well_bottom_clearance.dispense = 7
        p300m.pick_up_tip()
        for i in range(12):
            p300m.aspirate(60, trough.wells()[trough_well])
            p300m.move_to(trough.wells()[trough_well].top(-20))
            protocol.delay(seconds=1)
            p300m.dispense(60, plate_vt.wells()[8 * i])
            protocol.delay(seconds=1)
            p300m.blow_out(plate_vt.wells()[8 * i].top())
        p300m.drop_tip()
        p300m.well_bottom_clearance.dispense = 1
        protocol.home()

        #Step 4: Add 60ul Dil.Sample to VT plate + mix.
        for i in range(12):
            p300m.pick_up_tip()
            p300m.mix(5, 60, plate_dil.wells()[8 * i])
            p300m.blow_out(plate_dil.wells()[8 * i].top())
            p300m.transfer(60,
                           plate_dil.wells()[8 * i],
                           plate_vt.wells()[8 * i],
                           mix_after=(6, 80),
                           new_tip='never')
            protocol.delay(seconds=1)
            p300m.blow_out(plate_vt.wells()[8 * i].top())
            p300m.drop_tip()

    VT_SC_Plate(2, plate_dil_1, plate_vt_1)  #Call function to runn VT_SC_Plate
    protocol.home()
    VT_SC_Plate(3, plate_dil_2, plate_vt_2)
    protocol.home()
    VT_SC_Plate(4, plate_dil_3, plate_vt_3)
Example #16
0
def run(protocol: protocol_api.ProtocolContext):
    #if not protocol.is_simulating():
    protocol.home()
    # define Labware
    tiprack = protocol.load_labware('opentrons_96_tiprack_300ul', 10)
    pipette = protocol.load_instrument('p300_single_gen2',
                                       mount='left',
                                       tip_racks=[tiprack])

    # define Labware
    colorA_reservoir = protocol.load_labware('opentrons_96_tiprack_300ul', 9)
    colorBC_reservoir = protocol.load_labware('opentrons_96_tiprack_300ul', 6)
    mixing_plate = protocol.load_labware('corning_96_wellplate_360ul_flat', 4)
    dispense_plate = protocol.load_labware('corning_96_wellplate_360ul_flat',
                                           1)
    water_well = protocol.load_labware('corning_96_wellplate_360ul_flat', 8)
    cleaning_well = protocol.load_labware('corning_96_wellplate_360ul_flat', 7)

    # define Source Color Positions
    colorA_position = colorA_reservoir['F8']
    colorA_tip_position = tiprack['B1']

    colorB_position = colorBC_reservoir['A2']
    colorB_tip_position = tiprack['B2']

    colorC_position = colorBC_reservoir['E7']
    colorC_tip_position = tiprack['B3']

    # the example code below would go here, inside the run function

    df = pd.read_csv('/data/user_storage/color_test.csv')
    print(df.columns.values)
    colorA_amt = df.values[:, 0]
    print(colorA_amt)
    colorB_amt = df.values[:, 1]
    print(colorB_amt)
    colorC_amt = df.values[:, 2]
    print(colorC_amt)
    dispense_position = df.values[:, 3]
    print(dispense_position)

    #     pipette.pick_up_tip(tiprack['C1'])
    #     for i in range(len(dispense_position)):
    #         pipette.aspirate(270, water_well['C5'])
    #         pipette.dispense(270, mixing_plate[dispense_position[i]].top(5), rate=0.5)
    #     pipette.drop_tip(tiprack['C1'])

    for amt, src_pos, tip_pos in zip(
        [colorA_amt, colorB_amt, colorC_amt],
        [colorA_position, colorB_position, colorC_position],
        [colorA_tip_position, colorB_tip_position, colorC_tip_position]):
        pipette.pick_up_tip(tip_pos)
        amt_sum = sum(amt)
        #pipette.aspirate(amt_sum, src_pos.top(-35), rate=0.5)
        for i in range(len(dispense_position)):
            pipette.aspirate(55, water_well['C5'])
            pipette.aspirate(amt[i], src_pos.top(-35), rate=0.5)
            pipette.air_gap(10)
            pipette.dispense(amt[i] + 55 + 10,
                             mixing_plate[dispense_position[i]])
            pipette.blow_out(mixing_plate[dispense_position[i]].top(10))
            pipette.mix(1, 300, cleaning_well['C5'])
            pipette.blow_out()

        #pipette.blow_out(src_pos.top(30))
        pipette.drop_tip(tip_pos)

    pipette.pick_up_tip(tiprack['C1'])
    for i in range(len(dispense_position)):
        pipette.mix(1, 300, mixing_plate[dispense_position[i]])
        pipette.aspirate(300, mixing_plate[dispense_position[i]])
        pipette.dispense(300, mixing_plate[dispense_position[i]])
        pipette.blow_out(mixing_plate[dispense_position[i]].top(10))
        pipette.aspirate(80, mixing_plate[dispense_position[i]])
        pipette.air_gap(5)
        pipette.dispense(120, dispense_plate[dispense_position[i]], rate=0.5)
        pipette.aspirate(50, dispense_plate[dispense_position[i]])
        pipette.dispense(120, dispense_plate[dispense_position[i]], rate=0.5)
        pipette.mix(1, 300, cleaning_well['C5'])
        pipette.blow_out()

    pipette.drop_tip(tiprack['C1'])
def run(protocol: protocol_api.ProtocolContext):
    # labware
    plate_pel = protocol.load_labware('nunc_96_ubottom', '9') 
    plate_dil = protocol.load_labware('nunc_96_ubottom', '6')
    plate_ip = protocol.load_labware('iprasense_48_slide', '3')
    trough = protocol.load_labware('axygen_12_reservior_22ml', '8')
    tip300_1 = protocol.load_labware('opentrons_96_tiprack_300ul', '7')
    tip300_2 = protocol.load_labware('opentrons_96_tiprack_300ul', '10')
    tip200_1 = protocol.load_labware('opentrons_96_filtertiprack_200ul', '11')

    plate24_1A = protocol.load_labware('nunc_24_pseudo_a', '1')
    plate24_2B = protocol.load_labware('nunc_24_pseudo_b', '2')
    plate24_3A = protocol.load_labware('nunc_24_pseudo_a', '4')
    plate24_4B = protocol.load_labware('nunc_24_pseudo_b', '5')
    
    # pipettes (& settings if different to defaults)
    p300m = protocol.load_instrument('p300_multi', mount='right', tip_racks=[tip300_1, tip300_2])
    p50m = protocol.load_instrument('p50_multi', mount='left', tip_racks=[tip200_1])


    #Protocol Start!!!:
    #Step 1: Fill Dilution plate with 30ul media per well
    p50m.pick_up_tip()
    for i in range(12):
        p50m.aspirate(35, trough.wells('A1'))
        p50m.dispense(30, plate_dil.columns()[i])
        protocol.delay(seconds=0.5)
        p50m.blow_out(trough.wells('A1'))
    p50m.return_tip()
    p50m.reset_tipracks()

    #Step 2: Mix and transfer 300ul 24wp_culture 1 into supernatant plate, transfer 30ul into dilution plate
    List_plate = [(0, plate24_1A, tip300_1, 0, 0), (3, plate24_1A, tip300_1, 1, 0), (0, plate24_2B, tip300_2, 0, 0), (3, plate24_2B, tip300_2, 1, 0), (0, plate24_3A, tip300_1, 2, 6), (3, plate24_3A, tip300_1, 3, 6), (0, plate24_4B, tip300_2, 2, 6), (3, plate24_4B, tip300_2, 3, 6)]
                    #In list arguments 1-4 are: 24SWP number, 24SWP name, tiprack, column offset adjuster
    p300m.well_bottom_clearance.aspirate = 1.0
    p300m.well_bottom_clearance.dispense = 2.5  
    for (j, plate24, tip, k, l), i in product(List_plate, range(3)):    #'produt' multiplies two variables into a matrix, same as doubble loop.   
        isource = plate24.wells()[4*(i+j)]                            #isource and isource 2 are the same location, defined by wells or columns
        idest_intermediate = plate_dil.wells()[8*(i+j+l)]                      # save the source and destinations to variables based on 'i', 'j' 'k', 'l'
        idest_wb = plate_pel.wells()[8*k]

        well_edge_x1 = isource.bottom().move(types.Point(x=5.5, y=0, z=1.5))   # define 4 edges of the well for mixing
        well_edge_x2 = isource.bottom().move(types.Point(x=-5.5, y=0, z=1.5))
        well_edge_y1 = isource.bottom().move(types.Point(x=0, y=3.75, z=1.5))
        well_edge_y2 = isource.bottom().move(types.Point(x=0, y=-3.75, z=1.5))

        
        p300m.pick_up_tip(tip['A' + str(i+k*3+1)]) #Chooses tip to pick up, as cant slect only rack
        p300m.mix(1, 300, isource, rate=2.0)
        for r in range(2):                      # 2 loops of mixing each well edge
            p300m.aspirate(290, well_edge_x1, rate=2.0)
            p300m.dispense(300, well_edge_x2, rate=3.0)
            p300m.aspirate(290, well_edge_y1, rate=2.0)
            p300m.dispense(300, well_edge_y2, rate=3.0)            
            p300m.aspirate(290, well_edge_x2, rate=2.0)
            p300m.dispense(300, well_edge_x1, rate=3.0)            
            p300m.aspirate(290, well_edge_y2, rate=2.0)
            p300m.dispense(300, well_edge_y1, rate=3.0)
        p300m.mix(1, 300, isource, rate=2.0)
        protocol.delay(seconds=1.0)
        p300m.blow_out(isource.top())   
        p300m.transfer(30, isource, idest_intermediate, mix_after=(2, 45), new_tip='never')
        protocol.delay(seconds=1)
        p300m.blow_out(idest_intermediate.top())
        p300m.mix(1, 300, isource, rate=2.0)
        protocol.delay(seconds=1.0)
        p300m.blow_out(isource.top())
        p300m.transfer(75, isource, idest_wb, new_tip='never')
        protocol.delay(seconds=1)
        p300m.blow_out(idest_wb.top())
        p300m.drop_tip()
    p300m.well_bottom_clearance.aspirate = 1.0
    p300m.well_bottom_clearance.dispense = 1.0 

    #Intervention 1:
    protocol.home()
    protocol.pause("ATTENTION: Replace 'tiprack-300-1' in 'slot 7' as follows: Cols 1+2 odd rows, 3+4 even rows, rest of rack full. Replace 'tiprack-300-2' in slot 2 with a full rack. Insert IP slide into slot 3 for odd wells. Add feed to trough.A3")
    p300m.reset_tipracks()
    
     #Step 4: Feed Plates
    def Feed_Plate(plate_name):
        p300m.pick_up_tip()
        for i in range(6):
            p300m.aspirate(10, trough.wells()[2].top())
            p300m.aspirate(140, trough.wells()[2], rate=0.7)
            p300m.move_to(trough.wells()[2].top(-20))
            protocol.delay(seconds=1.0)
            p300m.dispense(150, plate_name.wells()[4*i])
            protocol.delay(seconds=1.0)
            p300m.blow_out(plate_name.wells()[4*i].top())
        p300m.drop_tip()

    p300m.well_bottom_clearance.aspirate = 1.0
    p300m.well_bottom_clearance.dispense = 15.0  
    Feed_Plate(plate24_1A)
    Feed_Plate(plate24_3A)
    Feed_Plate(plate24_2B)
    Feed_Plate(plate24_4B)

    p300m.well_bottom_clearance.aspirate = 1.0
    p300m.well_bottom_clearance.dispense = 1.0 
    protocol.home()
    
    #Function_create: Load into IP_slide, x indicates column miltipier on 96well plate, value = 0 or 1
    def IP_slide_load(x):
        for i in range(6):
            p50m.pick_up_tip()
            p50m.mix(8, 50, plate_dil.wells()[8*(2*i+x)], rate=4.0)
            protocol.delay(seconds=1) 
            p50m.blow_out(plate_dil.wells()[8*(2*i+x)].top())
            p50m.aspirate(10, plate_dil.wells()[8*(2*i+x)].top())
            p50m.flow_rate.aspirate = 25
            p50m.flow_rate.dispense = 2.5
            p50m.aspirate(9.0, plate_dil.wells()[8*(2*i+x)])
            p50m.dispense(30, plate_ip.wells()[8*(2*i+x)])
            protocol.delay(seconds=1.0)
            p50m.flow_rate.aspirate = 50
            p50m.flow_rate.dispense = 100
            p50m.drop_tip()

    #Step 3: Load into IP_slide 1, swaps slides, load IP_slide 2
    IP_slide_load(0)
    protocol.pause()
    protocol.comment("ATTENTION: Replace loaded iprasense slide with empty slide. Once complete click resume. Once protocol resumes, run IP_slide one on Iprasense")
    IP_slide_load(1)
    protocol.home()
    protocol.pause()
    protocol.comment("ATTENTION: Replace dilution plate with empty nunc_96U to take samples for qPCR")
    
    #Step 4: Seperate 60ul from WB samples for qPCR
    for i in range(4):
        p300m.pick_up_tip()
        p300m.mix(4, 200, plate_pel.wells()[8*i])
        protocol.delay(seconds=1.0)
        p300m.blow_out(plate_pel.wells()[8*i].top())
        p300m.transfer(60, plate_pel.wells()[8*i], plate_dil.wells()[8*i], new_tip='never')
        protocol.delay(seconds=1)
        p300m.blow_out(plate_dil.wells()[8*i].top())
        p300m.drop_tip()

    protocol.home()
    protocol.pause()
    protocol.comment("ATTENTION: Remove western blot at qPCR plates, pellet cells and return. Add 8ml RNA_Later to trough A5. Add 8ml ice cold PBS to trough A6")
   
    #Step 5: Add PBC to western blot samples and mix, Add RNA later to qPCR samples
    p300m.well_bottom_clearance.aspirate = 1.0
    p300m.well_bottom_clearance.dispense = 9.0 
    p300m.flow_rate.aspirate = 150
    p300m.flow_rate.dispense = 50
    
    p300m.pick_up_tip()
    for i in range(4):
        p300m.transfer(100, trough.wells('A6'), plate_pel.wells()[8*i], new_tip='never')
        protocol.delay(seconds=1)
        p300m.blow_out(plate_pel.wells()[8*i].top())
    p300m.drop_tip()
    
    p300m.pick_up_tip()
    for i in range(4):
        p300m.transfer(100, trough.wells('A5'), plate_dil.wells()[8*i], new_tip='never')
        protocol.delay(seconds=1)
        p300m.blow_out(plate_dil.wells()[8*i].top())
    p300m.drop_tip()


    #END SCRIPT!!!
Example #18
0
def run(protocol: protocol_api.ProtocolContext):
    # labware
    plate_stock = protocol.load_labware('cornering_96_wellplate_500ul', '9')
    plate_dna = protocol.load_labware('nunc_96_ubottom', '6')
    plate_nuc = protocol.load_labware('lonza_96_electroporation', '3')
    trough = protocol.load_labware('axygen_12_reservior_22ml', '8')
    tip200_1 = protocol.load_labware('opentrons_96_filtertiprack_200ul', '7')
    tip200_2 = protocol.load_labware('opentrons_96_filtertiprack_200ul', '10')
    tip200_3 = protocol.load_labware('opentrons_96_filtertiprack_200ul', '11')

    plate24_1A = protocol.load_labware('nunc_24_pseudo_a', '1')
    plate24_2B = protocol.load_labware('nunc_24_pseudo_b', '2')
    plate24_3A = protocol.load_labware('nunc_24_pseudo_a', '4')
    plate24_4B = protocol.load_labware('nunc_24_pseudo_b', '5')

    # pipettes (& settings if different to defaults)
    p300m = protocol.load_instrument('p300_multi',
                                     mount='right',
                                     tip_racks=[tip200_1, tip200_2])
    p300m.flow_rate.aspirate = 100
    p300m.flow_rate.dispense = 200
    p300m.maximum_volume = 200
    p300m.minimum_volume = 15

    p50m = protocol.load_instrument('p50_multi',
                                    mount='left',
                                    tip_racks=[tip200_1, tip200_3])

    #Step 1: Distrubute Nuc Solution to DNA plate
    p50m.pick_up_tip()
    for i in range(4):
        p50m.aspirate(45, trough.wells()[0])
        p50m.dispense(38, plate_dna.wells()[8 * i])
        protocol.delay(seconds=1)
        p50m.blow_out(trough.wells()[0].top())
    p50m.drop_tip()

    #Step 2: Distrubute DNA/RNA mix to DNA Plate
    for i in range(4):
        p50m.pick_up_tip()
        p50m.mix(3, 50, plate_stock.wells()[8 * i])
        protocol.delay(seconds=1)
        p50m.blow_out(plate_stock.wells()[8 * i].top())
        p50m.transfer(7,
                      plate_stock.columns()[i],
                      plate_dna.columns()[i],
                      mix_after=(1, 40),
                      new_tip='never')
        protocol.delay(seconds=1)
        p50m.blow_out(plate_dna.wells()[8 * i].top())
        p50m.drop_tip()

    # Intervention 1: Insert pause to comfirm cells ready in trough column 2
    protocol.pause()
    protocol.comment(
        'ATTENTION: Ensure below criteris are met prior to resuming protocol. Resuspended cells added to trough, position 8, column 2. Once complete click resume'
    )

    #Step 3: Mix cells + transfer to DNA plate
    p300m.pick_up_tip()
    p300m.well_bottom_clearance.aspirate = 2
    p300m.well_bottom_clearance.dispense = 10
    for j in range(15):
        p300m.aspirate(190, trough.wells_by_name()['A2'], rate=3.0)
        p300m.dispense(190, trough.wells_by_name()['A2'], rate=3.0)
    protocol.delay(seconds=1)
    p300m.blow_out(trough.wells_by_name()['A2'].top())
    p300m.touch_tip()
    p300m.drop_tip()
    p300m.flow_rate.aspirate = 150
    p300m.flow_rate.dispense = 200
    p300m.well_bottom_clearance.aspirate = 1
    p300m.well_bottom_clearance.dispense = 1

    for i in range(4):
        p50m.pick_up_tip()
        p50m.well_bottom_clearance.aspirate = 2
        p50m.well_bottom_clearance.dispense = 10
        for j in range(5):
            p50m.aspirate(50, trough.wells_by_name()['A2'], rate=5.0)
            p50m.dispense(50, trough.wells_by_name()['A2'], rate=5.0)
        p50m.well_bottom_clearance.aspirate = 1
        p50m.well_bottom_clearance.dispense = 1
        protocol.delay(seconds=1)
        p50m.blow_out(trough.wells_by_name()['A2'].top())
        p50m.transfer(45,
                      trough.wells('A2'),
                      plate_dna.wells()[8 * i],
                      new_tip='never',
                      mix_after=(2, 50),
                      touch_tip=True)
        protocol.delay(seconds=1)
        p50m.blow_out(plate_dna.wells()[8 * i])
        p50m.drop_tip()

    #Step 4: Distribute cells to nucleofection plate
    for i in range(4):  # loop for 4 columns on DNA setup plate
        p50m.pick_up_tip()
        p50m.mix(10, 50, plate_dna.wells()[8 * i], rate=5.0)
        protocol.delay(seconds=1)
        p50m.blow_out(plate_dna.wells()[8 * i].top())
        for j in range(3):  # Subloop for 3 replicates
            p50m.mix(4, (50 - 10 * j), plate_dna.wells()[8 * i], rate=5.0)
            p50m.blow_out(plate_dna.wells()[8 * i].top())
            protocol.delay(seconds=1.0)
            p50m.well_bottom_clearance.dispense = 2.5
            p50m.aspirate(25, plate_dna.wells()[8 * i])
            p50m.dispense(20, plate_nuc.wells()[8 * (3 * i + j)])
            protocol.delay(seconds=1.5)
            p50m.well_bottom_clearance.dispense = 1
            p50m.blow_out(plate_dna.wells()[8 * i].top())
        p50m.drop_tip()

    #Intervention 3: Insert pause for electroporation
    protocol.pause()
    protocol.comment(
        'ATTENTION: Ensure below criteris are met prior to resuming protocol. Perform electroporation and return nucleofection plate to position 3. During electroporation, add pregassed media to trough, position 8, col 3. Once complete click resume.'
    )

    #Step 5: Add 80ul media to all wells
    p300m.well_bottom_clearance.dispense = 12
    p300m.pick_up_tip()
    for i in range(12):
        p300m.aspirate(80, trough.wells()[2])
        p300m.dispense(80, plate_nuc.wells()[8 * i])
        protocol.delay(seconds=1)
        p300m.blow_out(plate_nuc.wells()[8 * i].top())
    p300m.drop_tip()
    p300m.well_bottom_clearance.dispense = 1
    protocol.home()

    #Intervention 4: Reset tipracks for seeding
    protocol.pause()
    protocol.comment(
        'ATTENTION: Ensure below criteria are met prior to resuming protocol. Please reset tipracks as detailed below ready for seeding of cells. tip200_1 (slot 7) Only odd rows or tips. tip200_2 (slot 10) requires only even rows of tips. Extended details in ReadME section of script. Once complete click resume.'
    )
    p300m.reset_tipracks()  # Reset tipracks

    #Step 8: Seed into 24SWPs
    List_plate = [(0, plate24_1A, tip200_1), (0, plate24_2B, tip200_2),
                  (6, plate24_3A, tip200_1), (6, plate24_4B, tip200_2)]
    #In list arguments 1-4 are: 24SWP number, 24SWP name, tiprack, column offset adjuster

    for (j, plate24, tip), i in product(
            List_plate, range(6)
    ):  #'produt' multiplies two variables into a matrix, same as doubble loop.
        isource = plate24.wells()[4 * (
            i
        )]  #isource and isource 2 are the same location, defined by wells or columns
        idest_nuc = plate_nuc.wells()[8 * (i + j)]

        p300m.well_bottom_clearance.aspirate = 2.5
        p300m.well_bottom_clearance.dispense = 2.5
        p300m.pick_up_tip(tip['A' + str(i + j + 1)])
        for m in range(10):
            p300m.aspirate(70, idest_nuc, rate=2.0)
            p300m.dispense(70, idest_nuc, rate=2.0)
        protocol.delay(seconds=1.0)
        p300m.blow_out(plate_nuc.wells()[8 * (j + i)].top())
        protocol.delay(seconds=1.0)
        p300m.aspirate(70, idest_nuc, rate=0.5)
        p300m.well_bottom_clearance.aspirate = 1.0
        p300m.well_bottom_clearance.dispense = 1.0
        p300m.dispense(70, isource)
        p300m.mix(2, 190, isource)
        protocol.delay(seconds=1.0)
        p300m.blow_out(isource.top())
        p300m.drop_tip()
Example #19
0
def run(ctx: protocol_api.ProtocolContext):

    #Change light to red
    ctx._hw_manager.hardware.set_lights(button=(1, 0, 0))

    ctx.comment('Actual used columns: ' + str(num_cols))
    STEP = 0
    STEPS = {  #Dictionary with STEP activation, description, and times
        1: {
            'Execute': True,
            'description': 'Transfer beads + PK + binding'
        },
        2: {
            'Execute': True,
            'description': 'Transfer wash'
        },
        3: {
            'Execute': True,
            'description': 'Transfer ethanol'
        },
        4: {
            'Execute': True,
            'description': 'Transfer elution'
        }
    }

    #Folder and file_path for log time
    import os
    folder_path = '/var/lib/jupyter/notebooks' + run_id
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/Station_B_Preparacion_Kingfisher_time_log.txt'

    #Define Reagents as objects with their properties
    class Reagent:
        def __init__(self,
                     name,
                     flow_rate_aspirate,
                     flow_rate_dispense,
                     flow_rate_aspirate_mix,
                     flow_rate_dispense_mix,
                     air_gap_vol_bottom,
                     air_gap_vol_top,
                     disposal_volume,
                     rinse,
                     max_volume_allowed,
                     reagent_volume,
                     reagent_reservoir_volume,
                     num_wells,
                     h_cono,
                     v_fondo,
                     tip_recycling='none',
                     dead_vol=700):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.flow_rate_aspirate_mix = flow_rate_aspirate_mix
            self.flow_rate_dispense_mix = flow_rate_dispense_mix
            self.air_gap_vol_bottom = air_gap_vol_bottom
            self.air_gap_vol_top = air_gap_vol_top
            self.disposal_volume = disposal_volume
            self.rinse = bool(rinse)
            self.max_volume_allowed = max_volume_allowed
            self.reagent_volume = reagent_volume
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.tip_recycling = tip_recycling
            self.dead_vol = dead_vol
            self.vol_well_original = (reagent_reservoir_volume / num_wells
                                      ) + dead_vol if num_wells > 0 else 0

    #Reagents and their characteristics
    Wash = Reagent(
        name='Wash',
        flow_rate_aspirate=5,  # Original = 0.5
        flow_rate_dispense=5,  # Original = 1
        flow_rate_aspirate_mix=
        1,  # Liquid density very high, needs slow aspiration
        flow_rate_dispense_mix=
        1,  # Liquid density very high, needs slow dispensation
        air_gap_vol_bottom=5,
        air_gap_vol_top=0,
        disposal_volume=1,
        rinse=True,
        max_volume_allowed=180,
        reagent_volume=
        WASH_VOLUME_PER_SAMPLE,  # reagent volume needed per sample
        reagent_reservoir_volume=(NUM_SAMPLES + 5) *
        WASH_VOLUME_PER_SAMPLE,  #70000, #51648
        num_wells=1,
        h_cono=1.95,
        v_fondo=695)  #1.95 * multi_well_rack_area / 2, #Prismatic

    Ethanol = Reagent(
        name='Ethanol',
        flow_rate_aspirate=5,
        flow_rate_dispense=5,
        flow_rate_aspirate_mix=1,
        flow_rate_dispense_mix=1,
        air_gap_vol_bottom=5,
        air_gap_vol_top=0,
        disposal_volume=1,
        rinse=True,
        max_volume_allowed=180,
        reagent_volume=ETHANOL_VOLUME_PER_SAMPLE,
        reagent_reservoir_volume=(NUM_SAMPLES + 5) * ETHANOL_VOLUME_PER_SAMPLE,
        num_wells=1,
        h_cono=1.95,
        v_fondo=695)  #1.95 * multi_well_rack_area / 2, #Prismatic

    Beads_PK_Binding = Reagent(
        name='Magnetic beads + PK + Binding',
        flow_rate_aspirate=0.5,
        flow_rate_dispense=0.5,
        flow_rate_aspirate_mix=0.5,
        flow_rate_dispense_mix=0.5,
        air_gap_vol_bottom=5,
        air_gap_vol_top=0,
        disposal_volume=1,
        rinse=True,
        max_volume_allowed=180,
        reagent_volume=BEADS_VOLUME_PER_SAMPLE,
        reagent_reservoir_volume=NUM_SAMPLES * BEADS_VOLUME_PER_SAMPLE * 1.1,
        num_wells=math.ceil(NUM_SAMPLES * BEADS_VOLUME_PER_SAMPLE * 1.1 /
                            11500),
        h_cono=1.95,
        v_fondo=695)  #1.95 * multi_well_rack_area / 2, #Prismatic

    Elution = Reagent(
        name='Elution',
        flow_rate_aspirate=3,  # Original 0.5
        flow_rate_dispense=3,  # Original 1
        flow_rate_aspirate_mix=15,
        flow_rate_dispense_mix=25,
        air_gap_vol_bottom=5,
        air_gap_vol_top=0,
        disposal_volume=1,
        rinse=False,
        max_volume_allowed=180,
        reagent_volume=ELUTION_VOLUME_PER_SAMPLE,
        reagent_reservoir_volume=(NUM_SAMPLES + 5) * ELUTION_VOLUME_PER_SAMPLE,
        num_wells=math.ceil(
            (NUM_SAMPLES + 5) * ELUTION_VOLUME_PER_SAMPLE / 13000),
        h_cono=1.95,
        v_fondo=695)  #1.95 * multi_well_rack_area / 2, #Prismatic

    Sample = Reagent(
        name='Sample',
        flow_rate_aspirate=3,  # Original 0.5
        flow_rate_dispense=3,  # Original 1
        flow_rate_aspirate_mix=15,
        flow_rate_dispense_mix=25,
        air_gap_vol_bottom=5,
        air_gap_vol_top=0,
        disposal_volume=1,
        rinse=False,
        max_volume_allowed=150,
        reagent_volume=50,
        reagent_reservoir_volume=(NUM_SAMPLES + 5) * 50,  #14800,
        num_wells=num_cols,  #num_cols comes from available columns
        h_cono=4,
        v_fondo=4 * math.pi * 4**3 / 3)  #Sphere

    Wash.vol_well = Wash.vol_well_original
    Ethanol.vol_well = Ethanol.vol_well_original
    Beads_PK_Binding.vol_well = Beads_PK_Binding.vol_well_original
    Elution.vol_well = Elution.vol_well_original
    Sample.vol_well = 350  # Arbitrary value

    def str_rounded(num):
        return str(int(num + 0.5))

    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('VOLUMES FOR ' + str(NUM_SAMPLES) + ' SAMPLES')
    ctx.comment(' ')
    ctx.comment('Beads + PK + Binding: ' + str(Beads_PK_Binding.num_wells) +
                ' wells from well 2 in multi reservoir with volume ' +
                str_rounded(Beads_PK_Binding.vol_well_original) +
                ' uL each one')
    ctx.comment('Elution: ' + str(Elution.num_wells) +
                ' wells from well 7 in multi reservoir with volume ' +
                str_rounded(Elution.vol_well_original) + ' uL each one')
    ctx.comment('Wash: in reservoir 1 with volume ' +
                str_rounded(Wash.vol_well_original) + ' uL')
    ctx.comment('Etanol: in reservoir 2 with volume ' +
                str_rounded(Ethanol.vol_well_original) + ' uL')
    ctx.comment('###############################################')
    ctx.comment(' ')

    ###################
    #Custom functions
    def custom_mix(pipet,
                   reagent,
                   location,
                   vol,
                   rounds,
                   blow_out,
                   mix_height,
                   offset,
                   wait_time=0,
                   drop_height=-1,
                   two_thirds_mix_bottom=False):
        '''
        Function for mix in the same location a certain number of rounds. Blow out optional. Offset
        can set to 0 or a higher/lower value which indicates the lateral movement
        '''
        if mix_height <= 0:
            mix_height = 1
        pipet.aspirate(1,
                       location=location.bottom(z=mix_height),
                       rate=reagent.flow_rate_aspirate_mix)
        for i in range(rounds):
            pipet.aspirate(vol,
                           location=location.bottom(z=mix_height),
                           rate=reagent.flow_rate_aspirate_mix)
            if two_thirds_mix_bottom and i < ((rounds / 3) * 2):
                pipet.dispense(vol,
                               location=location.bottom(z=5).move(
                                   Point(x=offset)),
                               rate=reagent.flow_rate_dispense_mix)
            else:
                pipet.dispense(vol,
                               location=location.top(z=drop_height).move(
                                   Point(x=offset)),
                               rate=reagent.flow_rate_dispense_mix)
        pipet.dispense(1,
                       location=location.bottom(z=mix_height),
                       rate=reagent.flow_rate_dispense_mix)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out
        if wait_time != 0:
            ctx.delay(seconds=wait_time,
                      msg='Waiting for ' + str(wait_time) + ' seconds.')

    def calc_height(reagent,
                    cross_section_area,
                    aspirate_volume,
                    min_height=0.4):
        nonlocal ctx
        ctx.comment('Remaining volume ' + str(reagent.vol_well) +
                    '< needed volume ' + str(aspirate_volume) + '?')
        if (reagent.vol_well - reagent.dead_vol) < aspirate_volume:
            ctx.comment('Next column should be picked')
            ctx.comment('Previous to change: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('After change: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('New volume:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume -
                      reagent.v_cono) / cross_section_area
            #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Remaining volume:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume -
                      reagent.v_cono) / cross_section_area
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Calculated height is ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('Used height is ' + str(height))
            col_change = False
        return height, col_change

    def move_vol_multi(pipet,
                       reagent,
                       source,
                       dest,
                       vol,
                       x_offset_source,
                       x_offset_dest,
                       pickup_height,
                       rinse,
                       avoid_droplet,
                       wait_time,
                       blow_out,
                       touch_tip=False,
                       drop_height=-5):
        # Rinse before aspirating
        if rinse == True:
            #pipet.aspirate(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap
            custom_mix(pipet,
                       reagent,
                       location=source,
                       vol=vol,
                       rounds=20,
                       blow_out=False,
                       mix_height=3,
                       offset=0)
            #pipet.dispense(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_dispense)

        # SOURCE
        if reagent.air_gap_vol_top != 0:  #If there is air_gap_vol, switch pipette to slow speed
            pipet.move_to(source.top(z=0))
            pipet.air_gap(reagent.air_gap_vol_top)  #air gap
            #pipet.aspirate(reagent.air_gap_vol_top, source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap

        s = source.bottom(pickup_height).move(Point(x=x_offset_source))
        pipet.aspirate(vol, s,
                       rate=reagent.flow_rate_aspirate)  # aspirate liquid

        if reagent.air_gap_vol_bottom != 0:  #If there is air_gap_vol, switch pipette to slow speed
            pipet.move_to(source.top(z=0))
            pipet.air_gap(reagent.air_gap_vol_bottom)  #air gap
            #pipet.aspirate(air_gap_vol_bottom, source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap

        if wait_time != 0:
            ctx.delay(seconds=wait_time,
                      msg='Waiting for ' + str(wait_time) + ' seconds.')

        if avoid_droplet == True:  # Touch the liquid surface to avoid droplets
            ctx.comment("Moving to: " + str(round(pickup_height, 2)) + ' mm')
            pipet.move_to(source.bottom(pickup_height))

        # GO TO DESTINATION
        d = dest.top(z=drop_height).move(Point(x=x_offset_dest))
        pipet.dispense(vol - reagent.disposal_volume +
                       reagent.air_gap_vol_bottom,
                       d,
                       rate=reagent.flow_rate_dispense)

        if wait_time != 0:
            ctx.delay(seconds=wait_time,
                      msg='Waiting for ' + str(wait_time) + ' seconds.')

        if reagent.air_gap_vol_top != 0:
            pipet.dispense(reagent.air_gap_vol_top,
                           dest.top(z=0),
                           rate=reagent.flow_rate_dispense)

        if blow_out == True:
            pipet.blow_out(dest.top(z=-5))

        if touch_tip == True:
            pipet.touch_tip(speed=20, v_offset=-10, radius=0.7)

        #if reagent.air_gap_vol_bottom != 0:
        #pipet.move_to(dest.top(z = 0))
        #pipet.air_gap(reagent.air_gap_vol_bottom) #air gap
        #pipet.aspirate(air_gap_vol_bottom, dest.top(z = 0),rate = reagent.flow_rate_aspirate) #air gap

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        #if not ctx.is_simulating():
        if tip_track['counts'][pip] >= tip_track['maxes'][pip]:
            ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \
            resuming.')
            pip.reset_tipracks()
            tip_track['counts'][pip] = 0
        pip.pick_up_tip()

    ##########
    def find_side(col):
        if col % 2 == 0:
            side = -1  # left
        else:
            side = 1  # right
        return side

# load labware and modules
############################################
######## Sample plate - comes from A

    deepwell_plate_samples = ctx.load_labware('nest_96_wellplate_2ml_deep',
                                              '1',
                                              'NEST 96 Deepwell Plate 2mL 1')
    deepwell_plate_wash = ctx.load_labware('nest_96_wellplate_2ml_deep', '5',
                                           'NEST 96 Deepwell Plate 2mL 2')
    deepwell_plate_ethanol = ctx.load_labware('nest_96_wellplate_2ml_deep',
                                              '6',
                                              'NEST 96 Deepwell Plate 2mL 3')
    deepwell_plate_elution = ctx.load_labware(
        'biorad_96_wellplate_200ul_pcr', '7',
        'Bio-Rad 96 Well Plate 200 µL PCR')

    ####################################
    ######## 12 well rack
    reagent_multi_res = ctx.load_labware('nest_12_reservoir_15ml', '4',
                                         'Reagent deepwell plate')

    ####################################
    ######## Single reservoirs
    reagent_res_1 = ctx.load_labware('nest_1_reservoir_195ml', '2',
                                     'Single reagent reservoir 1')
    res_1 = reagent_res_1.wells()[0]

    reagent_res_2 = ctx.load_labware('nest_1_reservoir_195ml', '3',
                                     'Single reagent reservoir 2')
    res_2 = reagent_res_2.wells()[0]

    ####################################
    ######### Load tip_racks
    tips300 = [
        ctx.load_labware('opentrons_96_tiprack_300ul', slot,
                         '200µl filter tiprack') for slot in ['8', '9']
    ]

    ###############################################################################
    #Declare which reagents are in each reservoir as well as deepwell and sample plate
    Wash.reagent_reservoir = res_1
    Ethanol.reagent_reservoir = res_2
    Beads_PK_Binding.reagent_reservoir = reagent_multi_res.rows()[0][1:5]
    Elution.reagent_reservoir = reagent_multi_res.rows()[0][6:7]
    work_destinations = deepwell_plate_samples.rows()[0][:Sample.num_wells]
    wash_destinations = deepwell_plate_wash.rows()[0][:Sample.num_wells]
    ethanol_destinations = deepwell_plate_ethanol.rows()[0][:Sample.num_wells]
    elution_destinations = deepwell_plate_elution.rows()[0][:Sample.num_wells]

    # pipettes.
    m300 = ctx.load_instrument('p300_multi_gen2', 'right',
                               tip_racks=tips300)  # Load multi pipette

    #### used tip counter and set maximum tips available
    tip_track = {
        'counts': {
            m300: 0
        },
        'maxes': {
            m300: 96 * len(m300.tip_racks)
        }  #96 tips per tiprack * number or tipracks in the layout
    }

    ###############################################################################

    ###############################################################################
    ###############################################################################
    # STEP 1 TRANSFER BEADS + PK + Binding
    ########
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        beads_trips = math.ceil(Beads_PK_Binding.reagent_volume /
                                Beads_PK_Binding.max_volume_allowed)
        beads_volume = Beads_PK_Binding.reagent_volume / beads_trips  #136.66
        beads_transfer_vol = []
        for i in range(beads_trips):
            beads_transfer_vol.append(beads_volume +
                                      Beads_PK_Binding.disposal_volume)
        x_offset_source = 0
        x_offset_dest = 0
        rinse = False  # Original: True
        first_mix_done = False

        for i in range(num_cols):
            ctx.comment("Column: " + str(i))
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(beads_transfer_vol):
                #Calculate pickup_height based on remaining volume and shape of container
                [pickup_height,
                 change_col] = calc_height(Beads_PK_Binding,
                                           multi_well_rack_area,
                                           transfer_vol * 8)
                if change_col == True or not first_mix_done:  #If we switch column because there is not enough volume left in current reservoir column we mix new column
                    ctx.comment('Mixing new reservoir column: ' +
                                str(Beads_PK_Binding.col))
                    custom_mix(m300,
                               Beads_PK_Binding,
                               Beads_PK_Binding.reagent_reservoir[
                                   Beads_PK_Binding.col],
                               vol=Beads_PK_Binding.max_volume_allowed,
                               rounds=BEADS_WELL_FIRST_TIME_NUM_MIXES,
                               blow_out=False,
                               mix_height=0.5,
                               offset=0)
                    first_mix_done = True
                else:
                    ctx.comment('Mixing reservoir column: ' +
                                str(Beads_PK_Binding.col))
                    custom_mix(m300,
                               Beads_PK_Binding,
                               Beads_PK_Binding.reagent_reservoir[
                                   Beads_PK_Binding.col],
                               vol=Beads_PK_Binding.max_volume_allowed,
                               rounds=BEADS_WELL_NUM_MIXES,
                               blow_out=False,
                               mix_height=0.5,
                               offset=0)
                ctx.comment('Aspirate from reservoir column: ' +
                            str(Beads_PK_Binding.col))
                ctx.comment('Pickup height is ' +
                            str(round(pickup_height, 2)) + ' mm')
                #if j!=0:
                #    rinse = False
                move_vol_multi(m300,
                               reagent=Beads_PK_Binding,
                               source=Beads_PK_Binding.reagent_reservoir[
                                   Beads_PK_Binding.col],
                               dest=work_destinations[i],
                               vol=transfer_vol,
                               x_offset_source=x_offset_source,
                               x_offset_dest=x_offset_dest,
                               pickup_height=pickup_height,
                               rinse=rinse,
                               avoid_droplet=False,
                               wait_time=2,
                               blow_out=True,
                               touch_tip=True,
                               drop_height=-1)
            ctx.comment(' ')
            ctx.comment('Mixing sample ')
            custom_mix(m300,
                       Beads_PK_Binding,
                       location=work_destinations[i],
                       vol=Beads_PK_Binding.max_volume_allowed,
                       rounds=BEADS_NUM_MIXES,
                       blow_out=False,
                       mix_height=0,
                       offset=0,
                       wait_time=2,
                       two_thirds_mix_bottom=True)
            # m300.move_to(work_destinations[i].top(0))
            # m300.air_gap(Beads_PK_Binding.air_gap_vol_bottom) #air gap
            if recycle_tip == True:
                m300.return_tip()
            else:
                m300.drop_tip(home_after=False)
            tip_track['counts'][m300] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)
        ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300]))
        ###############################################################################
        # STEP 1 TRANSFER BEADS + PK + Binding
        ########

    ###############################################################################
    # STEP 2 TRANSFER WASH
    ########
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        wash_trips = math.ceil(Wash.reagent_volume / Wash.max_volume_allowed)
        wash_volume = Wash.reagent_volume / wash_trips  #136.66
        wash_transfer_vol = []
        for i in range(wash_trips):
            wash_transfer_vol.append(wash_volume + Wash.disposal_volume)
        x_offset_source = 0
        x_offset_dest = 0
        rinse = False
        for i in range(num_cols):
            ctx.comment("Column: " + str(i))
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)

            for j, transfer_vol in enumerate(wash_transfer_vol):
                ctx.comment('Aspirate from reservoir 1')
                #if j!=0:
                #    rinse = False
                move_vol_multi(m300,
                               reagent=Wash,
                               source=Wash.reagent_reservoir,
                               dest=wash_destinations[i],
                               vol=transfer_vol,
                               x_offset_source=x_offset_source,
                               x_offset_dest=x_offset_dest,
                               pickup_height=2,
                               rinse=rinse,
                               avoid_droplet=False,
                               wait_time=0,
                               blow_out=True,
                               touch_tip=True)
            ctx.comment(' ')
        if recycle_tip == True:
            m300.return_tip()
        else:
            m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)
        ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300]))
        ###############################################################################
        # STEP 2 TRANSFER WASH
        ########

    ###############################################################################
    # STEP 3 TRANSFER ETHANOL
    ########
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        ethanol_trips = math.ceil(Ethanol.reagent_volume /
                                  Ethanol.max_volume_allowed)
        ethanol_volume = Ethanol.reagent_volume / ethanol_trips  #136.66
        ethanol_transfer_vol = []
        for i in range(ethanol_trips):
            ethanol_transfer_vol.append(ethanol_volume +
                                        Ethanol.disposal_volume)
        x_offset_source = 0
        x_offset_dest = 0
        rinse = False
        for i in range(num_cols):
            ctx.comment("Column: " + str(i))
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)

            for j, transfer_vol in enumerate(ethanol_transfer_vol):
                ctx.comment('Aspirate from reservoir 2')
                #if j!=0:
                #    rinse = False
                move_vol_multi(m300,
                               reagent=Ethanol,
                               source=Ethanol.reagent_reservoir,
                               dest=ethanol_destinations[i],
                               vol=transfer_vol,
                               x_offset_source=x_offset_source,
                               x_offset_dest=x_offset_dest,
                               pickup_height=2,
                               rinse=rinse,
                               avoid_droplet=False,
                               wait_time=0,
                               blow_out=True,
                               touch_tip=True)

        if recycle_tip == True:
            m300.return_tip()
        else:
            m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)
        ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300]))
        ###############################################################################
        # STEP 3 TRANSFER ETHANOL
        ########

    ###############################################################################
    # STEP 4 TRANSFER ELUTION
    ########
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        elution_trips = math.ceil(Elution.reagent_volume /
                                  Elution.max_volume_allowed)
        elution_volume = Elution.reagent_volume / elution_trips  #136.66
        elution_transfer_vol = []
        for i in range(elution_trips):
            elution_transfer_vol.append(elution_volume +
                                        Elution.disposal_volume)
        x_offset_source = 0
        x_offset_dest = 0
        rinse = False

        for i in range(num_cols):
            ctx.comment("Column: " + str(i))
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(elution_transfer_vol):
                #Calculate pickup_height based on remaining volume and shape of container
                [pickup_height,
                 change_col] = calc_height(Elution, multi_well_rack_area,
                                           transfer_vol * 8)
                ctx.comment('Aspirate from reservoir column: ' +
                            str(Elution.col))
                ctx.comment('Pickup height is ' +
                            str(round(pickup_height, 2)) + ' mm')
                #if j!=0:
                #    rinse = False
                move_vol_multi(m300,
                               reagent=Elution,
                               source=Elution.reagent_reservoir[Elution.col],
                               dest=elution_destinations[i],
                               vol=transfer_vol,
                               x_offset_source=x_offset_source,
                               x_offset_dest=x_offset_dest,
                               pickup_height=pickup_height,
                               rinse=rinse,
                               avoid_droplet=False,
                               wait_time=0,
                               blow_out=False,
                               touch_tip=True)
            ctx.comment(' ')

        if recycle_tip == True:
            m300.return_tip()
        else:
            m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)
        ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300]))
        ###############################################################################
        # STEP 4 TRANSFER ELUTION
        ########

    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('Homing robot')
    ctx.comment('###############################################')
    ctx.comment(' ')
    ctx.home()
    ###############################################################################
    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write(
                'STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    # Light flash end of program
    import os
    #os.system('mpg123 /etc/audio/speaker-test.mp3')
    for i in range(3):
        ctx._hw_manager.hardware.set_lights(rails=False)
        ctx._hw_manager.hardware.set_lights(button=(1, 0, 0))
        time.sleep(0.3)
        ctx._hw_manager.hardware.set_lights(rails=True)
        ctx._hw_manager.hardware.set_lights(button=(0, 0, 1))
        time.sleep(0.3)
    ctx._hw_manager.hardware.set_lights(button=(0, 1, 0))
    ctx.comment('Finished! \nMove deepwell plates Kingfisher robot.')
    ctx.comment('Used tips 200uL in total: ' + str(tip_track['counts'][m300]))
    ctx.comment('Used racks 200uL in total: ' +
                str(tip_track['counts'][m300] / 96))
    ctx.comment('Available 200uL tips: ' + str(tip_track['maxes'][m300]))
def run(ctx: protocol_api.ProtocolContext):
    global MM_TYPE

    ctx.comment(
        "Protocollo Preparazione Mastermix Bioer per {} campioni.".format(
            NUM_SAMPLES))

    # check source (elution) labware type
    tips20 = [
        ctx.load_labware('opentrons_96_filtertiprack_20ul', slot)
        for slot in ['2', '3', '5', '6', '8', '9', '11']
    ]

    tips300 = [ctx.load_labware('opentrons_96_filtertiprack_200ul', '10')]
    pcr_plate = ctx.load_labware(
        'opentrons_96_aluminumblock_biorad_wellplate_200ul', '1', 'PCR plate')
    mm_strips = ctx.load_labware(
        'opentrons_96_aluminumblock_generic_pcr_strip_200ul', '4',
        'mastermix strips')
    tube_block = ctx.load_labware(
        'opentrons_24_aluminumblock_nest_2ml_snapcap', '7',
        '2ml snap tube aluminum block for mastermix + controls')

    # pipette
    m20 = ctx.load_instrument('p20_multi_gen2', 'right', tip_racks=tips20)
    p300 = ctx.load_instrument('p300_single_gen2', 'left', tip_racks=tips300)

    # setup up sample sources and destinations
    num_cols = math.ceil(NUM_SAMPLES / 8)
    sample_dests = pcr_plate.rows()[0][NUM_COLONNA:num_cols + NUM_COLONNA]
    control_dest1 = pcr_plate.wells()[88]  #controlli in posizione A12 e H12
    control_dest2 = pcr_plate.wells()[95]

    tip_log = {'count': {}}
    folder_path = '/data/C'
    tip_file_path = folder_path + '/tip_log.json'
    if TIP_TRACK and not ctx.is_simulating():
        if os.path.isfile(tip_file_path):
            with open(tip_file_path) as json_file:
                data = json.load(json_file)
                if 'tips20' in data:
                    tip_log['count'][m20] = data['tips20']
                else:
                    tip_log['count'][m20] = 0
                if 'tips300' in data:
                    tip_log['count'][p300] = data['tips300']
                else:
                    tip_log['count'][p300] = 0
        else:
            tip_log['count'] = {m20: 0, p300: 0}
    else:
        tip_log['count'] = {m20: 0, p300: 0}

    tip_log['tips'] = {
        m20: [tip for rack in tips20 for tip in rack.rows()[0]],
        p300: [tip for rack in tips300 for tip in rack.wells()]
    }
    tip_log['max'] = {pip: len(tip_log['tips'][pip]) for pip in [m20, p300]}

    def pick_up(pip):
        nonlocal tip_log
        if tip_log['count'][pip] == tip_log['max'][pip]:
            # print('Replace ' + str(pip.max_volume) + 'µl tipracks before resuming.')
            ctx.pause('Replace ' + str(pip.max_volume) +
                      'µl tipracks before resuming.')
            pip.reset_tipracks()
            tip_log['count'][pip] = 0
        pip.pick_up_tip(tip_log['tips'][pip][tip_log['count'][pip]])
        tip_log['count'][pip] += 1

    """ mastermix component maps """
    # setup tube mastermix
    ctx.comment("Mastermix per sample: {}".format(MM_PER_SAMPLE))
    ctx.comment("Num samples: {}".format(NUM_SAMPLES))
    ctx.comment("Liquid headroom: {}".format(liquid_headroom))
    ctx.comment("Tube capacity: {}".format(mm_tube_capacity))

    num_mm_tubes = math.ceil(
        ((MM_PER_SAMPLE * NUM_SAMPLES) + liquid_headroom) / mm_tube_capacity)
    samples_per_mm_tube = []

    for i in range(num_mm_tubes):
        remaining_samples = NUM_SAMPLES - sum(samples_per_mm_tube)
        samples_per_mm_tube.append(
            min(8 * math.ceil(remaining_samples / (8 * (num_mm_tubes - i))),
                remaining_samples))
    NUM_MM = NUM_SAMPLES + 4
    mm_per_tube = MM_PER_SAMPLE * NUM_MM * 1.1

    mm_tube = tube_block.wells()[:num_mm_tubes]
    ctx.comment(
        "Mastermix: caricare {} tube con almeno {}ul di mastermix".format(
            num_mm_tubes, mm_per_tube))

    # setup strips mastermix
    mm_strip = mm_strips.columns()[:num_mm_tubes]

    mm_indices = list(
        chain.from_iterable(
            repeat(i, ns) for i, ns in enumerate(samples_per_mm_tube)))
    """START REPEATED SECTION"""
    p300.flow_rate.aspirate = MM_RATE_ASPIRATE
    p300.flow_rate.dispense = MM_RATE_DISPENSE
    m20.flow_rate.aspirate = MM_RATE_ASPIRATE
    m20.flow_rate.dispense = MM_RATE_DISPENSE
    for i in range(NUM_SEDUTE):
        ctx.comment("Seduta {}/{}".format(i + 1, NUM_SEDUTE))
        # transfer mastermix to strips
        pick_up(p300)
        for mt, ms, ns in zip(mm_tube, mm_strip, samples_per_mm_tube):
            for strip_i, strip_w in enumerate(ms):
                p300.transfer((ns // 8 + (1 if strip_i < ns % 8 else 0)) *
                              MM_PER_SAMPLE * 1.1,
                              mt.bottom(0.7),
                              strip_w,
                              new_tip='never')
        p300.transfer(MM_PER_SAMPLE,
                      mm_tube[1],
                      control_dest1.bottom(2),
                      new_tip='never')
        p300.transfer(MM_PER_SAMPLE,
                      mm_tube[1],
                      control_dest2.bottom(2),
                      new_tip='never')
        p300.drop_tip()

        # transfer mastermix to plate
        for m_idx, s in zip(mm_indices[::8], sample_dests):
            pick_up(m20)
            m20.transfer(MM_PER_SAMPLE,
                         mm_strip[m_idx][0].bottom(3.5),
                         s.bottom(3.5),
                         new_tip='never')
            m20.drop_tip()

        if i < NUM_SEDUTE - 1:
            blight = BlinkingLight(ctx=ctx)
            blight.start()
            ctx.home()
            ctx.pause(
                "Togliere la pcr plate e preparare l'occorrente per la prossima seduta."
            )
            blight.stop()
        else:
            blight = BlinkingLight(ctx=ctx)
            blight.start()
            ctx.home()
            ctx.pause("Togliere la pcr plate.")
            blight.stop()
    """END REPEATED SECTION"""

    # track final used tip
    if TIP_TRACK and not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        data = {
            'tips20': tip_log['count'][m20],
            'tips300': tip_log['count'][p300]
        }
        with open(tip_file_path, 'w') as outfile:
            json.dump(data, outfile)
Example #21
0
def run(ctx: protocol_api.ProtocolContext):
    ctx.comment('Actual used columns: ' + str(num_cols))
    #from opentrons.drivers.rpi_drivers import gpio
    # Define the STEPS of the protocol
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description, and times
        1: {'Execute': True, 'description': 'Add 50 ul Elution Buffer'},
        2: {'Execute': True, 'description': 'Add 100 ul Wash Buffer 1 - Round 1'},
        3: {'Execute': True, 'description': 'Add 100 ul Wash Buffer 2 - Round 1 and stop until plate comes from A'},
        4: {'Execute': True, 'description': 'Add 100 ul Lysis Buffer and then move to station A'},
        5: {'Execute': False, 'description': 'Transfer IC'},
        6: {'Execute': True, 'description': 'Transfer ICtwo'},
        7: {'Execute': False, 'description': 'Mix beads'},
        8: {'Execute': False, 'description': 'Transfer beads'},
        9: {'Execute': True, 'description': 'Mix beadstwo'},
        10: {'Execute': True, 'description': 'Transfer beadstwo'}
        }

    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0
    folder_path = '/var/lib/jupyter/notebooks/'+str(run_id)
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/KB_station_viral_time_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse,
                     reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo,
                      tip_recycling = 'none', rinse_loops = 3, flow_rate_dispense_mix = 4, flow_rate_aspirate_mix = 4):

            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.flow_rate_aspirate_mix = flow_rate_aspirate_mix
            self.flow_rate_dispense_mix = flow_rate_dispense_mix
            self.rinse = bool(rinse)
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.delay = delay #Delay of reagent in dispense
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.unused=[]
            self.tip_recycling = tip_recycling
            self.vol_well_original = reagent_reservoir_volume / num_wells
            self.rinse_loops = rinse_loops


    # Reagents and their characteristics
    WashBuffer1 = Reagent(name='Wash Buffer 1',
                          flow_rate_aspirate=0.75,
                          flow_rate_dispense=1,
                          rinse=True,
                          rinse_loops=6,
                          delay=3,
                          reagent_reservoir_volume=$Wone_total_volume, #100*NUM_SAMPLES,
                          num_wells=$Wone_wells,
                          h_cono=1.95,
                          v_fondo=695)  # Flat surface

    WashBuffer2 = Reagent(name='Wash Buffer 2',
                          flow_rate_aspirate=0.75,
                          flow_rate_dispense=1,
                          rinse=True,
                          delay=3,
                          reagent_reservoir_volume=$Wtwo_total_volume, #100*NUM_SAMPLES,
                          num_wells=$Wtwo_wells,
                          h_cono=1.95,
                          v_fondo=695)  # Flat surface

    Lysis = Reagent(name='Lysis Buffer',
                          flow_rate_aspirate=0.75,
                          flow_rate_dispense=0.5,
                          rinse=False,
                          rinse_loops=6,
                          delay=2,
                          reagent_reservoir_volume=$Lysis_total_volume, #100*NUM_SAMPLES,
                          num_wells=1,
                          h_cono=1.95,
                          v_fondo=695)  # Flat surface

    ElutionBuffer = Reagent(name='Elution Buffer',
                            flow_rate_aspirate=1,
                            flow_rate_dispense=1,
                            rinse=False,
                            delay=0,
                            reagent_reservoir_volume=$Elution_total_volume,#50*NUM_SAMPLES,
                            num_wells=$Elution_wells,
                            h_cono=1.95,
                            v_fondo=695)  # Prismatic

    IC = Reagent(name='Magnetic beads and Lysis',
                    flow_rate_aspirate=1,
                    flow_rate_dispense=3,
                    rinse=False,
                    num_wells=$IC_wells,
                    delay=2,
                    reagent_reservoir_volume=$IC_total_volume,#20 * NUM_SAMPLES * 1.1,
                    h_cono=1.95,
                    v_fondo=695)  # Prismatic

    ICtwo = Reagent(name='Internal control 2',
                    flow_rate_aspirate=1,
                    flow_rate_dispense=3,
                    rinse=False,
                    num_wells=1,
                    delay=2,
                    reagent_reservoir_volume=$IC_total_volume,#20 * NUM_SAMPLES * 1.1,
                    h_cono=1,
                    v_fondo=10)  # Prismatic

    Beads = Reagent(name='Magnetic beads and Lysis',
                    flow_rate_aspirate=0.5,
                    flow_rate_dispense=0.5,
                    rinse=True,
                    rinse_loops=6,
                    num_wells=$Beads_wells,
                    delay=3,
                    reagent_reservoir_volume=$Beads_total_volume,#20 * NUM_SAMPLES * 1.1,
                    h_cono=1.95,
                    v_fondo=695)  # Prismatic

    Beadstwo = Reagent(name='Magnetic beads 2',
                    flow_rate_aspirate=0.5,
                    flow_rate_dispense=0.5,
                    rinse=True,
                    rinse_loops=4,
                    num_wells=2,
                    delay=3,
                    reagent_reservoir_volume=$Beads_total_volume,#20 * NUM_SAMPLES * 1.1,
                    h_cono=1.95,
                    v_fondo=695)  # Prismatic

    Beads.vol_well = Beads.vol_well_original
    IC.vol_well = IC.vol_well_original
    WashBuffer1.vol_well = WashBuffer1.vol_well_original
    WashBuffer2.vol_well = WashBuffer2.vol_well_original
    ElutionBuffer.vol_well = ElutionBuffer.vol_well_original
    Lysis.vol_well = Lysis.vol_well_original
    Beadstwo.vol_well = Beadstwo.vol_well_original
    ICtwo.vol_well = ICtwo.vol_well_original

    ##################
    # Custom functions
    def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset,
                       pickup_height, rinse, disp_height, blow_out, touch_tip,
                       post_dispense=False, post_dispense_vol=20,
                       post_airgap=True, post_airgap_vol=10):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet, reagent, location = source, vol = vol,
                       rounds = reagent.rinse_loops, blow_out = True, mix_height = 0,
                       x_offset = x_offset, post_airgap=False,post_dispense=False, post_dispense_vol=20,post_airgap_vol=10)
        # SOURCE
        s = source.bottom(pickup_height).move(Point(x = x_offset[0]))
        pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol, source.top(z = -2),
                           rate = reagent.flow_rate_aspirate)  # air gap
        # GO TO DESTINATION
        drop = dest.top(z = disp_height).move(Point(x = x_offset[1]))
        pipet.dispense(vol + air_gap_vol, drop,
                       rate = reagent.flow_rate_dispense)  # dispense all
        ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent
        if blow_out == True:
            pipet.blow_out(dest.top(z = -5))
        if post_airgap == True:
            pipet.aspirate(post_airgap_vol, dest.top(z = -2))
        if post_dispense == True:
            pipet.dispense(post_dispense_vol, dest.top(z = -2))
        if touch_tip == True:
            pipet.touch_tip(speed = 20, v_offset = -5, radius = 0.9)




    def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height,
    source_height = 3, post_airgap=True, post_airgap_vol=10,
    post_dispense=False, post_dispense_vol=20,x_offset = x_offset):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height == 0:
            mix_height = 3
        pipet.aspirate(1, location=location.bottom(
            z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate_mix)
        for _ in range(rounds):
            pipet.aspirate(vol, location=location.bottom(
                z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate_mix)
            pipet.dispense(vol, location=location.bottom(
                z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense_mix)
        pipet.dispense(1, location=location.bottom(
            z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense_mix)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out
        if post_dispense == True:
            pipet.dispense(post_dispense_vol, location.top(z = -2))
        if post_airgap == True:
            pipet.aspirate(post_airgap_vol, location.top(z = 5))

    def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.2, extra_volume = 50):
        nonlocal ctx
        ctx.comment('Remaining volume ' + str(reagent.vol_well) +
                    '< needed volume ' + str(aspirate_volume) + '?')
        if reagent.vol_well < aspirate_volume + extra_volume:
            reagent.unused.append(reagent.vol_well)
            ctx.comment('Next column should be picked')
            ctx.comment('Previous to change: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('After change: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('New volume:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area
                    #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Remaining volume:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Calculated height is ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('Used height is ' + str(height))
            col_change = False
        return height, col_change

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        if not ctx.is_simulating():
            if tip_track['counts'][pip] == tip_track['maxes'][pip]:
                ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \
                resuming.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0
        pip.pick_up_tip()
    ##########

    def find_side(col):
        '''
        Detects if the current column has the magnet at its left or right side
        '''
        if col % 2 == 0:
            side = -1  # left
        else:
            side = 1
        return side


####################################
    # load labware and modules

    # IC well rack
    ####################################
    #tempdeck = ctx.load_module('tempdeck', '3')
    ic_reservoir = ctx.load_labware(
        'nest_96_wellplate_100ul_pcr_full_skirt','3', 'Wellplate with Beads and IC')

    # 12 well rack
    ####################################
    reagent_res = ctx.load_labware(
        'nest_12_reservoir_15ml', '5', 'Reservoir 12 channel, column 1')

    # Wash Buffer 1 100ul Deepwell plate
    ############################################
    WashBuffer1_100ul_plate1 = ctx.load_labware(
        'kf_96_wellplate_2400ul', '1', 'Wash Buffer 1 Deepwell plate 1')

    # Wash Buffer 2 100ul Deepwell plate
    ############################################
    WashBuffer2_100ul_plate1 = ctx.load_labware(
        'kf_96_wellplate_2400ul', '4', 'Wash Buffer 2 Deepwell plate 1')

    # Lysis buffer 100ul Deepwell plate and later will be the plate with samples
    ############################################
    kf_plate = ctx.load_labware(
        'kf_96_wellplate_2400ul', '2', 'Sample Deepwell plate 1')

    # Elution Deepwell plate
    ############################################
    ElutionBuffer_50ul_plate = ctx.load_labware(
        'kingfisher_std_96_wellplate_550ul', '7', 'Elution Buffer 50 ul STD plate')


####################################
    # Load tip_racks
    tips300 = [ctx.load_labware('opentrons_96_tiprack_300ul', slot, '200µl filter tiprack')
               for slot in ['8']]
    tips20 = [ctx.load_labware('opentrons_96_filtertiprack_20ul', slot, '20µl filter tiprack')
               for slot in ['6','9']]

################################################################################
    # Declare which reagents are in each reservoir as well as deepwell and elution plate
    WashBuffer1.reagent_reservoir = reagent_res.rows(
    )[0][:1] # position 1

    WashBuffer2.reagent_reservoir = reagent_res.rows(
    )[0][1:2] #position 2

    ElutionBuffer.reagent_reservoir = reagent_res.rows(
    )[0][9:10] #position 10

    Lysis.reagent_reservoir = reagent_res.rows(
    )[0][2:3] #position 3

    IC.reagent_reservoir = reagent_res.rows(
    )[0][10:11] #position 4

    Beads.reagent_reservoir = reagent_res.rows(
    )[0][11:] #position 4

    ICtwo.reagent_reservoir = ic_reservoir.rows(
    )[0][:1] #position 1

    Beadstwo.reagent_reservoir = ic_reservoir.rows(
    )[0][1:3] #position 2 to 3

    # columns in destination plates to be filled depending the number of samples
    wb1plate1_destination = WashBuffer1_100ul_plate1.rows()[0][:num_cols]
    wb2plate1_destination = WashBuffer2_100ul_plate1.rows()[0][:num_cols]
    elutionbuffer_destination = ElutionBuffer_50ul_plate.rows()[0][:num_cols]
    kf_destination = kf_plate.rows()[0][:num_cols]

    # pipette
    m300 = ctx.load_instrument(
        'p300_multi_gen2', 'right', tip_racks=tips300)  # Load multi pipette

    # pipettes. P1000 currently deactivated
    m20 = ctx.load_instrument(
        'p20_multi_gen2', 'left', tip_racks=tips20)  # Load p300 multi pipette

    # used tip counter and set maximum tips available
    tip_track = {
        'counts': {m300: 0, m20: 0},
        'maxes': {m300: len(tips300)*96, m20: len(tips20)*96}
    }

    ############################################################################
    # STEP 1: Transfer Elution buffer
    ############################################################################

    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        # Elution buffer
        ElutionBuffer_vol = [Elution_vol]

        ########
        # Water or elution buffer
        for i in range(num_cols):
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for transfer_vol in ElutionBuffer_vol:
                # Calculate pickup_height based on remaining volume and shape of container
                move_vol_multichannel(m300, reagent = ElutionBuffer, source = ElutionBuffer.reagent_reservoir[ElutionBuffer.col],
                              dest = elutionbuffer_destination[i], vol = transfer_vol,
                              air_gap_vol = air_gap_vol_elutionbuffer, x_offset = x_offset,
                              pickup_height = 0.2, rinse = False, disp_height = -2,
                              blow_out = True, touch_tip = True, post_airgap=True)
        m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)


    ############################################################################
    # STEP 2: Filling with WashBuffer1
    ############################################################################

    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        WB1 = [WBone_vol]
        rinse = False  # Only first time
        ########
        # Wash buffer dispense
        for i in range(num_cols):
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(WB1):
                if (i == 0 and j == 0):
                    rinse = True #Rinse only first transfer
                else:
                    rinse = False
                move_vol_multichannel(m300, reagent = WashBuffer1, source = WashBuffer1.reagent_reservoir[WashBuffer1.col],
                               dest = wb1plate1_destination[i], vol = transfer_vol,
                               air_gap_vol = air_gap_vol, x_offset = x_offset,
                               pickup_height = 0.3, rinse = rinse, disp_height = -2,
                               blow_out = True, touch_tip = False, post_airgap=True)

        m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 3: Filling with WashBuffer2
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        WB2 = [WBtwo_vol]
        rinse = False  # Only first time
        ########
        # Wash buffer dispense
        for i in range(num_cols):
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(WB2):
                if (i == 0 and j == 0):
                    rinse = True
                else:
                    rinse = False
                move_vol_multichannel(m300, reagent = WashBuffer2, source = WashBuffer2.reagent_reservoir[WashBuffer2.col],
                               dest = wb2plate1_destination[i], vol = transfer_vol,
                               air_gap_vol = air_gap_vol, x_offset = x_offset,
                               pickup_height = 0.2, rinse = rinse, disp_height = -2,
                               blow_out = True, touch_tip = False, post_airgap=True)
        m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8
        ctx.pause('Bring plate from A in position 2 and click continue. Put IC and Beads in position 3')
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 4: Filling with Lysis
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        lysis_vol = [100]
        rinse = False  # Only first time

        ########
        # Wash buffer dispense
        for i in range(num_cols):
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(lysis_vol):
                if (i == 0 and j == 0):
                    rinse = True #Rinse only first transfer
                else:
                    rinse = False
                move_vol_multichannel(m300, reagent = Lysis, source = Lysis.reagent_reservoir[Lysis.col],
                               dest = kf_destination[i], vol = transfer_vol,
                               air_gap_vol = air_gap_vol, x_offset = x_offset,
                               pickup_height = 0.2, rinse = rinse, disp_height = -2,
                               blow_out = True, touch_tip = False, post_airgap=True,post_dispense=True)

        m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 5: TRANSFER IC
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        # Transfer parameters
        start = datetime.now()
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        IC_transfer_vol = [ic_vol]
        rinse = True
        for i in range(num_cols):
            if not m20.hw_pipette['has_tip']:
                pick_up(m20)
            for j, transfer_vol in enumerate(IC_transfer_vol):
                move_vol_multichannel(m20, reagent=IC, source=IC.reagent_reservoir[IC.col],
                                      dest=kf_destination[i], vol=transfer_vol,
                                      air_gap_vol=air_gap_ic, x_offset=[0,0],
                                      pickup_height=0.2, disp_height = -40.7,
                                      rinse=IC.rinse, blow_out = True, touch_tip=False, post_airgap=True)

                m20.drop_tip(home_after=False)
                tip_track['counts'][m20] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 6: TRANSFER IC2
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        # Transfer parameters
        start = datetime.now()
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        IC_transfer_vol = [ic_vol]
        rinse = True
        for i in range(num_cols):
            if not m20.hw_pipette['has_tip']:
                pick_up(m20)
            for j, transfer_vol in enumerate(IC_transfer_vol):
                move_vol_multichannel(m20, reagent=ICtwo, source=ICtwo.reagent_reservoir[IC.col],
                                      dest=kf_destination[i], vol=transfer_vol,
                                      air_gap_vol=air_gap_ic, x_offset=[0,0],
                                      pickup_height=0.5, disp_height = -40.7,
                                      rinse=ICtwo.rinse, blow_out = True, touch_tip=False, post_airgap=True)

                m20.drop_tip(home_after=False)
                tip_track['counts'][m20] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 7: PREMIX BEADS
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:

        start = datetime.now()
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        if not m300.hw_pipette['has_tip']:
            pick_up(m300)
            ctx.comment('Tip picked up')
        ctx.comment('Mixing ' + Beads.name)

        # Mixing
        custom_mix(m300, Beads, Beads.reagent_reservoir[Beads.col], vol=120,
                   rounds=10, blow_out=True, mix_height=10, post_dispense=True, source_height=0.3)
        ctx.comment('Finished premixing!')
        ctx.comment('Now, reagents will be transferred to deepwell plate.')

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 8: TRANSFER BEADS
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        # Transfer parameters
        start = datetime.now()
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        beads_transfer_vol = [beads_vol]  # Two rounds of 130
        rinse = True
        for i in range(num_cols):
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(beads_transfer_vol):
                if (i == 0 and j == 0):
                    rinse = True
                else:
                    rinse = False
                # Calculate pickup_height based on remaining volume and shape of container
                [pickup_height, change_col] = calc_height(
                    reagent = Beads, cross_section_area = multi_well_rack_area,
                    aspirate_volume = transfer_vol * 8, min_height=0.3, extra_volume=10)
                if change_col == True:  # If we switch column because there is not enough volume left in current reservoir column we mix new column
                    ctx.comment(
                        'Mixing new reservoir column: ' + str(Beads.col))
                    custom_mix(m300, Beads, Beads.reagent_reservoir[Beads.col],
                               vol=120, rounds=10, blow_out=True, mix_height=1,
                               post_dispense=True)
                ctx.comment(
                    'Aspirate from reservoir column: ' + str(Beads.col))
                ctx.comment('Pickup height is ' + str(pickup_height))
                move_vol_multichannel(m300, reagent=Beads, source=Beads.reagent_reservoir[Beads.col],
                                      dest=kf_destination[i], vol=transfer_vol,
                                      air_gap_vol=air_gap_vol, x_offset=x_offset,
                                      pickup_height=0.2, disp_height = -8,
                                      rinse=rinse, blow_out = True, touch_tip=False, post_airgap=True)

                '''custom_mix(m300, Beads, work_destinations_cols[i] ,
                                   vol=70, rounds=10, blow_out=True, mix_height=8,
                                   x_offset = x_offset, source_height=0.5, post_dispense=True)
                   m300.drop_tip(home_after=False)

                                   '''


        m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)


    ############################################################################
    # STEP 9: PREMIX BEADStwo
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:

        start = datetime.now()
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        if not m300.hw_pipette['has_tip']:
            pick_up(m300)
            ctx.comment('Tip picked up')
        ctx.comment('Mixing ' + Beadstwo.name)

        # Mixing
        custom_mix(m300, Beadstwo, Beadstwo.reagent_reservoir[Beadstwo.col], vol=80,
                   rounds=10, blow_out=True, mix_height=5, post_dispense=True, source_height=0.7)
        ctx.comment('Finished premixing!')
        ctx.comment('Now, reagents will be transferred to deepwell plate.')

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 10: TRANSFER BEADStwo
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        # Transfer parameters
        start = datetime.now()
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        beads_transfer_vol = [beads_vol]  # Two rounds of 130
        rinse = True
        for i in range(num_cols):
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(beads_transfer_vol):
                if (i == 0 and j == 0):
                    rinse = True
                else:
                    rinse = False
                # Calculate pickup_height based on remaining volume and shape of container
                [pickup_height, change_col] = calc_height(
                    reagent = Beadstwo, cross_section_area = 15,
                    aspirate_volume = transfer_vol , min_height=0.7, extra_volume=0) # I will consider only aspiration from one well
                pickup_height=0.7
                if change_col == True:  # If we switch column because there is not enough volume left in current reservoir column we mix new column
                    ctx.comment(
                        'Mixing new reservoir column: ' + str(Beadstwo.col))
                    custom_mix(m300, Beadstwo, Beadstwo.reagent_reservoir[Beadstwo.col],
                               vol=100, rounds=10, blow_out=True, mix_height=0.7,
                               post_dispense=True)

                ctx.comment(
                    'Aspirate from reservoir column: ' + str(Beadstwo.col))
                ctx.comment('Pickup height is ' + str(pickup_height))
                move_vol_multichannel(m300, reagent=Beadstwo, source=Beadstwo.reagent_reservoir[Beadstwo.col],
                                      dest=kf_destination[i], vol=transfer_vol,
                                      air_gap_vol=air_gap_vol, x_offset=x_offset,
                                      pickup_height=pickup_height, disp_height = -8,
                                      rinse=rinse, blow_out = True, touch_tip=False, post_airgap=True)

                '''custom_mix(m300, Beads, work_destinations_cols[i] ,
                                   vol=70, rounds=10, blow_out=True, mix_height=8,
                                   x_offset = x_offset, source_height=0.5, post_dispense=True)
                   m300.drop_tip(home_after=False)

                                   '''


        m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

############################################################################
    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write('STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    # Light flash end of program
    if not ctx.is_simulating():


        for i in range(3):
            ctx._hw_manager.hardware.set_lights(rails=False)
            time.sleep(0.3)
            ctx._hw_manager.hardware.set_lights(rails=True)
            time.sleep(0.3)
            ctx._hw_manager.hardware.set_lights(rails=False)
    ctx.home()

    ctx.comment(
        'Finished! \nMove deepwell plates to KingFisher extractor.')
    ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300]))
    ctx.comment('Used racks in total: ' + str(tip_track['counts'][m300] / 96))
Example #22
0
def run(protocol: protocol_api.ProtocolContext):
    """Function to transfer 100ul from 1 well to another."""

#loading labware (from opentrons labware library)
    plate = protocol.load_labware('biorad_96_wellplate_200ul_pcr', 1, 
        '96plate')
    tiprack_1 = protocol.load_labware('opentrons_96_tiprack_300ul', 2, 
        '300tips')
#load pipette 
    p300 = protocol.load_instrument('p300_single_gen2', 'right', 
        tip_racks=[tiprack_1])
    
#run protocol
    while protocol.door_closed:
        if not protocol.door_closed:
            protocol.set_rail_lights(True)
            protocol.home()
            protocol.pause('Press resume when you are ready :)!')        
    
        protocol.set_rail_lights(False)        
     
    #transfer 100ul from plate well A1 to plate well B1 with default settings    
        p300.pick_up_tip()
         #aspirate & dispense at default flow rate of 92.86 ul/s
        p300.aspirate(100, plate['A1'])
        p300.dispense(100, plate['B1'])
        p300.blow_out() #blow out in current location
        p300.drop_tip() #automatically in trash
        
    #all following pipetting steps with changed default flow rates
         #aspirate at 150 ul/s
        p300.flow_rate.aspirate = 150
         #dispense at 300 ul/s
        p300.flow_rate.dispense = 300
         #blow out at 200 ul/s
        p300.flow_rate.blow_out = 200  
        
        p300.pick_up_tip()
        p300.aspirate(100, plate['C1'])
        p300.dispense(100, plate['D1'])
        p300.blow_out(plate['D1']) #blow out in destination well
        p300.drop_tip() #automatically in trash
    
        
    #all following pipetting steps aspirate & dispense other height (in mm)
         #aspirate 2 mm from bottom
        p300.well_bottom_clearance.aspirate = 3
         #dispense 10 mm from bottom
        p300.well_bottom_clearance.dispense = 10 
        
        p300.pick_up_tip()
        p300.aspirate(100, plate['E1'])
        p300.dispense(100, plate['F1'])
        p300.blow_out(plate['F1']) #blow out in destination well
        p300.drop_tip() #automatically in trash
        
        #display a comment in the opentrons app
        protocol.comment('Hello you!')
    
    #all following pipetting steps with per-axis speed limits
         #limit x-axis to 50 mm/s
        protocol.max_speeds['X'] = 50
         #limit y-axis to 50 mm/s
        protocol.max_speeds['Y'] = 50
         #limit a-axis to 50 mm/s
        protocol.max_speeds['A'] = 50
        
        p300.pick_up_tip()
        p300.aspirate(100, plate['G1'])
        p300.dispense(100, plate['H1'])
        p300.blow_out(plate['H1']) #blow out in destination well
        p300.drop_tip() #automatically in trash
        
    #all following pipetting steps with per-axis speed limits to default
         #reset x-axis speed limit - delete method
        del protocol.max_speeds['X']
         #reset y-axis speed limit - none method
        protocol.max_speeds['Y'] = None
         #reset a-axis speed limit - none method
        protocol.max_speeds['A'] = None
        
        p300.pick_up_tip()
        p300.aspirate(100, plate['B1'])
        p300.dispense(100, plate['A1'])
        p300.blow_out(plate['A1']) #blow out in destination well
        p300.drop_tip() #automatically in trash
        
        break
Example #23
0
def run(protocol: protocol_api.ProtocolContext):
    if debug: print(protocol)

    tiprack_300 = protocol.load_labware('opentrons_96_tiprack_300ul',
                                        labwarePositions.tiprack_300,
                                        "tiprack 300ul")

    if debug: print(tiprack_300)

    pipette_300 = protocol.load_instrument('p300_single',
                                           'right',
                                           tip_racks=[tiprack_300])
    pipette_300.flow_rate.dispense = default_flow_rate
    pipette_300.flow_rate.aspirate = default_flow_rate
    pipette_300.starting_tip = tiprack_300.well(
        tiprack_starting_pos['tiprack_300'])

    if debug: print(pipette_300)

    black_96 = protocol.load_labware(type_of_96well_plate,
                                     labwarePositions.antibodies_plate,
                                     type_of_96well_plate)

    trough12 = protocol.load_labware('parhelia_12trough',
                                     labwarePositions.buffers_reservoir,
                                     '12-trough buffers reservoir')
    if (not retreaval):
        par2 = protocol.load_labware('par2s_9slides_blue_v2',
                                     labwarePositions.par2,
                                     'par2s_9slides_blue_v2')
    if retreaval:
        temp_mod = protocol.load_module('temperature module',
                                        labwarePositions.heatmodule)

        par2 = temp_mod.load_labware('par2s_9slides_blue_v2')

    if debug: print(par2)

    buffer_wells = trough12.wells_by_name()

    buffers = Object()
    buffers.retreaval = buffer_wells['A1']
    buffers.TBS_wash = buffer_wells['A2']
    buffers.water = buffer_wells['A3']
    buffers.storage = buffer_wells['A4']
    buffers.eth_70perc_ = buffer_wells['A5']
    buffers.eth_80perc = buffer_wells['A6']
    buffers.eth_95perc = buffer_wells['A7']
    buffers.eth_100perc = buffer_wells['A8']
    buffers.hematoxylin = buffer_wells['A12']

    preblock_wells = black_96.rows()[0]
    antibody_wells = black_96.rows()[1]
    enzymeblock_wells = black_96.rows()[2]
    hrpsecondaryab_wells = black_96.rows()[3]
    substrate_wells = black_96.rows()[4]
    DAB_wells = black_96.rows()[5]

    sample_chambers = []

    for well in wellslist:
        sample_chambers.append(par2.wells_by_name()[well])

    if debug: print(sample_chambers)

    #################PROTOCOL####################
    protocol.home()
    if retreaval:
        washSamples(pipette_300, buffers.retreaval, buffers.retreaval, 0, 1,
                    extra_bottom_gap)
        washSamples(pipette_300, buffers.retreaval, sample_chambers,
                    wash_volume, 2, extra_bottom_gap)
        temp_mod.set_temperature(95)
        print("retreaval")
        protocol.delay(minutes=15)
        washSamples(pipette_300, buffers.retreaval, sample_chambers,
                    wash_volume, 1, extra_bottom_gap)
        protocol.delay(minutes=15)
        washSamples(pipette_300, buffers.retreaval, sample_chambers,
                    wash_volume, 1, extra_bottom_gap)
        protocol.delay(minutes=15)
        print("cooling down to RT")
        temp_mod.set_temperature(25)
        protocol.delay(minutes=20)

    # WASHING SAMPLES WITH TBS
    print("washing in TBS")
    washSamples(pipette_300, buffers.TBS_wash, buffers.TBS_wash, 0, 1,
                extra_bottom_gap)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, 2,
                extra_bottom_gap)

    # Preblocking
    print("preblocking")
    print(len(wellslist))
    for i in range(len(wellslist)):
        print(i)
        washSamples(pipette_300, preblock_wells[i], sample_chambers[i],
                    wash_volume, 1, extra_bottom_gap)
    print("preblocking incubation: 15 min")
    protocol.delay(minutes=15)

    # APPLYING ANTIBODY COCKTAILS TO SAMPLES
    print("applying antibodies")
    for i in range(len(wellslist)):
        print(i)
        washSamples(pipette_300, antibody_wells[i], sample_chambers[i],
                    wash_volume, 1, extra_bottom_gap)

    # INCUBATE FOR DESIRED TIME
    print("staining incubation: " + str(primary_ab_incubation_time_minutes) +
          "min")
    protocol.delay(minutes=primary_ab_incubation_time_minutes)

    # WASHING SAMPLES WITH TBS
    # three individual repeats below is because they need particular incubation time between them
    print("washing with TBS")
    for i in range(5):
        washSamples(pipette_300, buffers.TBS_wash, sample_chambers,
                    wash_volume, 1, extra_bottom_gap)
        protocol.delay(minutes=3)

    # APPLYING enzyme blocking
    print("applying enzyme blocking")
    for i in range(len(wellslist)):
        washSamples(pipette_300, enzymeblock_wells[i], sample_chambers[i],
                    wash_volume, 1, extra_bottom_gap)
    # INCUBATE 10 MIN
    print("hrp blocking incubation: 10min")
    protocol.delay(minutes=10)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, 3,
                extra_bottom_gap)

    # APPLYING HRP SECONDARY ANTIBODY COCKTAILS TO SAMPLES
    print("applying hrpsecondaryab")
    for i in range(len(wellslist)):
        washSamples(pipette_300, hrpsecondaryab_wells[i], sample_chambers[i],
                    wash_volume, 1, extra_bottom_gap)

    # INCUBATE FOR DESIRED TIME
    print("staining incubation: " + str(secondary_ab_incubation_time_minutes) +
          "min")
    protocol.delay(minutes=secondary_ab_incubation_time_minutes)

    # three individual repeats below is because they need particular incubation time between them
    print("washing with TBS")
    for i in range(3):
        washSamples(pipette_300, buffers.TBS_wash, sample_chambers,
                    wash_volume, 1, extra_bottom_gap)
        protocol.delay(minutes=3)

    # DILUTING AND APPLYING THE DAB
    for i in range(len(wellslist)):
        dilute_and_apply_fixative(pipette_300, DAB_wells[i],
                                  substrate_wells[i], sample_chambers[i], 200)

    print("developing substrate")

    protocol.delay(minutes=10)

    washSamples(pipette_300, buffers.water, buffers.water, 0, 1,
                extra_bottom_gap)
    washSamples(pipette_300, buffers.water, sample_chambers, wash_volume, 5,
                extra_bottom_gap)

    # STORAGE, washing samples every hour
    washSamples(pipette_300, buffers.storage, buffers.storage, 0, 1,
                extra_bottom_gap)
    for i in range(48):
        washSamples(pipette_300,
                    buffers.storage,
                    sample_chambers,
                    wash_volume / 2,
                    1,
                    extra_bottom_gap,
                    keep_tip=True)
        protocol.delay(minutes=60)
        print("total dispensed volume: ", str(stats.volume))
Example #24
0
def run(protocol: protocol_api.ProtocolContext):

    if debug: print(protocol)

    tiprack_300 = protocol.load_labware('opentrons_96_tiprack_300ul', labwarePositions.tiprack_300, "tiprack 300ul")

    if debug: print(tiprack_300)

    pipette_300 =  protocol.load_instrument('p300_single', 'right', tip_racks = [tiprack_300])
    pipette_300.flow_rate.dispense = default_flow_rate
    pipette_300.flow_rate.aspirate = default_flow_rate
    pipette_300.starting_tip = tiprack_300.well(tiprack_starting_pos['tiprack_300'])

    if debug: print(pipette_300)

    par2_slides = protocol.load_labware('par2s_9slides_blue_v2', labwarePositions.par2, 'par2s_9slides_blue_v2')

#    trough12_def = json.loads(AXYGEN_12well_plate_DEF_JSON)

#    trough12 = protocol.load_labware_from_definition(trough12_def, labwarePositions.buffers_reservoir, '12-trough buffers reservoir')

#    custom_96_def = json.loads(CUSTOM_96well_plate_DEF_JSON)


    custom_96 = protocol.load_labware('parhelia_black_96', labwarePositions.antibodies_plate, 'parhelia_black_96')

    trough12 = protocol.load_labware('parhelia_12trough', labwarePositions.buffers_reservoir, 'parhelia_12trough')

    temp_mod = protocol.load_module('temperature module', '8')

    par2_on_heat_module=temp_mod.load_labware('par2s_9slides_blue_v2')


    if debug: print(par2)
    buffer_wells = trough12.wells_by_name()

    buffers = Object()
    buffers.retreaval =  buffer_wells['A1']
    buffers.TBS_wash =  buffer_wells['A2']
    buffers.water =  buffer_wells['A3']
    buffers.storage = buffer_wells['A4']


    preblock_wells_cycle1 = custom_96.rows()[0]
    antibody_wells_cycle1 = custom_96.rows()[1]
    opal_polymer_cycle1 = custom_96.rows()[2]
    opal_fluorophore1 = custom_96.rows()[3]
    preblock_wells_cycle2 = custom_96.rows()[4]
    antibody_wells_cycle2 = custom_96.rows()[5]
    opal_polymer_cycle2 = custom_96.rows()[6]
    opal_fluorophore2 = custom_96.rows()[7]



    sample_chambers = []

    for well in wellslist:
        sample_chambers.append(par2_on_heat_module.wells_by_name()[well])

    if debug: print(sample_chambers)

#################PROTOCOL####################
    protocol.home()

###-------------------- FIRST ROUND

#WASHING SAMPLES WITH TBS
    print("washing in TBS")
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=2)
#    protocol.delay(minutes=5)

    print("preblocking")
    for i in range (len(wellslist)):
        washSamples(pipette_300, preblock_wells_cycle1[i], sample_chambers[i], wash_volume)
#INCUBATE 15 MIN
    print("preblocking incubation: 15 min")
    protocol.delay(minutes=15)


#    protocol.delay(minutes=60)

#APPLYING ANTIBODY COCKTAILS TO SAMPLES
    print("applying antibodies")
    for i in range (len(wellslist)):
        washSamples(pipette_300, antibody_wells_cycle1[i], sample_chambers[i], wash_volume)

#INCUBATE 90 MIN
    print("staining incubation 1.5h")
    protocol.delay(minutes=90)

#    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=1)

#WASHING SAMPLES WITH TBS
#three individual repeats below is because they need particular incubation time between them
    print("washing with TBS")
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=2)
    protocol.delay(minutes=3)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=2)
    protocol.delay(minutes=3)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=2)
    protocol.delay(minutes=3)

#APPLYING OPAL polymer HRP
    print("applying opal secondary")
    for i in range (len(wellslist)):
     washSamples(pipette_300, opal_polymer_cycle1[i], sample_chambers[i], wash_volume)

#INCUBATE 10 MIN
    print("opal secondary for 10min")
    protocol.delay(minutes=10)

#three individual repeats below is because they need particular incubation time between them
    print("washing with TBS")
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=1)
    protocol.delay(minutes=3)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=1)
    protocol.delay(minutes=3)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=1)
    protocol.delay(minutes=3)

#Opal Signal generation
    print("Opal Signal generation")
    for i in range (len(wellslist)):
        washSamples(pipette_300, opal_fluorophore1[i], sample_chambers[i], wash_volume)

#INCUBATE 10 MIN
    print("opal fluorophore1 10min" )
    protocol.delay(minutes=10)

#WASHING SAMPLES WITH TBS
    print("washing in TBS")
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=2)
    protocol.delay(minutes=2)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=2)
    protocol.delay(minutes=2)

    washSamples(pipette_300, buffers.retreaval, sample_chambers, wash_volume, num_repeats=3)
    protocol.delay(minutes=3)

    temp_mod.set_temperature(95)

    print("retreaval")
    protocol.delay(minutes=40)

    temp_mod.set_temperature(25)

#    temp_mod.deactivate()

    print("cooling down")
    protocol.delay(minutes=20)

###--------cycle 2

#WASHING SAMPLES WITH TBS
    print("washing in TBS")
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=2)
#    protocol.delay(minutes=5)

#    pipette_300.drop_tip()

    print("preblocking")
    for i in range (len(wellslist)):
        washSamples(pipette_300, preblock_wells_cycle2[i], sample_chambers[i], wash_volume)
#INCUBATE 15 MIN
    print("preblocking incubation: 15 min")
    protocol.delay(minutes=15)


#    protocol.delay(minutes=60)

#APPLYING ANTIBODY COCKTAILS TO SAMPLES
    print("applying antibodies")
    for i in range (len(wellslist)):
        washSamples(pipette_300, antibody_wells_cycle2[i], sample_chambers[i], wash_volume)

#INCUBATE 90 MIN
    print("staining incubation 1.5h")
    protocol.delay(minutes=90)

#    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=1)

#WASHING SAMPLES WITH TBS
#three individual repeats below is because they need particular incubation time between them
    print("washing with TBS")
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=2)
    protocol.delay(minutes=3)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=2)
    protocol.delay(minutes=3)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=2)
    protocol.delay(minutes=3)

#APPLYING OPAL polymer HRP
    print("applying hrpsecondaryab")
    for i in range (len(wellslist)):
        washSamples(pipette_300, opal_polymer_cycle2[i], sample_chambers[i], wash_volume)

#INCUBATE 10 MIN
    print("opal secondary for 10min")
    protocol.delay(minutes=10)

#three individual repeats below is because they need particular incubation time between them
    print("washing with TBS")
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=1)
    protocol.delay(minutes=3)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=1)
    protocol.delay(minutes=3)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=1)
    protocol.delay(minutes=3)

#Opal Signal generation
    print("Opal Signal generation")
    for i in range (len(wellslist)):
        washSamples(pipette_300, opal_fluorophore2[i], sample_chambers[i], wash_volume)

#INCUBATE 10 MIN
    print("opal fluorophore1 10min" )
    protocol.delay(minutes=10)

#WASHING SAMPLES WITH TBS
    print("washing in TBS")
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=2)
    protocol.delay(minutes=2)
    washSamples(pipette_300, buffers.TBS_wash, sample_chambers, wash_volume, num_repeats=2)
    protocol.delay(minutes=2)
    washSamples(pipette_300, buffers.retreaval, sample_chambers, wash_volume, num_repeats=3)
    protocol.delay(minutes=3)

    temp_mod.set_temperature(95)

    print("retreaval")
    protocol.delay(minutes=40)

    temp_mod.set_temperature(25)

    temp_mod.deactivate()

    print("cooling down")
    protocol.delay(minutes=20)

#STORAGE, washing samples every hour
    for i in range (48):
        washSamples(pipette_300, buffers.storage,sample_chambers, 100)
        protocol.delay(minutes=60)
        print("total dispensed volume: ", str(stats.volume))
Example #25
0
def run(ctx: protocol_api.ProtocolContext):
    import os
    # Define the STEPS of the protocol
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description, and times
        1: {'Execute': True, 'description': 'Add samples ('+str(volume_sample)+'ul)'},
        2: {'Execute': False, 'description': 'Add internal control one by one (10ul)'}
        # We won't use it as we will do it in KB with the multichannel pipette
    }

    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0

    if not ctx.is_simulating():
        # Folder and file_path for log time
        folder_path = '/var/lib/jupyter/notebooks/'+run_id
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/KA_SampleSetup_viral_time_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse,
                     reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo,
                      tip_recycling = 'none',rinse_loops = 2):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.rinse = bool(rinse)
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.delay = delay
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.unused=[]
            self.tip_recycling = tip_recycling
            self.vol_well_original = reagent_reservoir_volume / num_wells
            self.rinse_loops = rinse_loops

    Samples = Reagent(name = 'Samples',
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      rinse = False,
                      delay = 0,
                      reagent_reservoir_volume = 100 * 24,
                      num_wells = 24,  # num_cols comes from available columns
                      h_cono = h_cone,
                      v_fondo = volume_cone
                      )  # cone

    IC = Reagent(name = 'Internal control',
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 3,
                      rinse = False,
                      delay = 0,
                      reagent_reservoir_volume = $IC_total_volume,
                      num_wells = $IC_wells,  # num_cols comes from available columns
                      h_cono = h_cone,
                      v_fondo = volume_cone
                      )  # cone

    Samples.vol_well = Samples.vol_well_original
    IC.vol_well = IC.vol_well_original

    ##################
    # Custom functions
    def generate_source_table(source):
        '''
        Concatenate the wells from the different origin racks
        '''
        for rack_number in range(len(source)):
            if rack_number == 0:
                s = source[rack_number].wells()
            else:
                s = s + source[rack_number].wells()
        return s

    def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset,
                       pickup_height, rinse, disp_height, blow_out, touch_tip,
                       post_dispense=False, post_dispense_vol=20,
                       post_airgap=False, post_airgap_vol=10):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet, reagent, location = source, vol = vol,
                       rounds = 2, blow_out = True, mix_height = 0,
                       x_offset = x_offset)
        # SOURCE
        s = source.bottom(pickup_height).move(Point(x = x_offset[0]))
        pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol, source.top(z = -2),
                           rate = reagent.flow_rate_aspirate)  # air gap
        # GO TO DESTINATION
        drop = dest.top(z = disp_height).move(Point(x = x_offset[1]))
        pipet.dispense(vol + air_gap_vol, drop,
                       rate = reagent.flow_rate_dispense)  # dispense all
        ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent
        if blow_out == True:
            pipet.blow_out(dest.top(z = -2))
        if post_dispense == True:
            pipet.dispense(post_dispense_vol, dest.top(z = -2))
        if touch_tip == True:
            pipet.touch_tip(speed = 20, v_offset = -5, radius = 0.9)
        if post_airgap == True:
            pipet.aspirate(post_airgap_vol, dest.top(z = 5))



    def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height,
    x_offset, source_height = 3, post_airgap=False, post_airgap_vol=10,
    post_dispense=False, post_dispense_vol=20,):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height == 0:
            mix_height = 3
        pipet.aspirate(1, location=location.bottom(
            z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate)
        for _ in range(rounds):
            pipet.aspirate(vol, location=location.bottom(
                z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate)
            pipet.dispense(vol, location=location.bottom(
                z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense)
        pipet.dispense(1, location=location.bottom(
            z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out
        if post_dispense == True:
            pipet.dispense(post_dispense_vol, location.top(z = -2))
        if post_airgap == True:
            pipet.dispense(post_airgap_vol, location.top(z = 5))

    def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.4, extra_volume = 50):
        nonlocal ctx
        ctx.comment('Remaining volume ' + str(reagent.vol_well) +
                    '< needed volume ' + str(aspirate_volume) + '?')
        if reagent.vol_well < aspirate_volume + extra_volume:
            reagent.unused.append(reagent.vol_well)
            ctx.comment('Next column should be picked')
            ctx.comment('Previous to change: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('After change: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('New volume:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area
                    #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Remaining volume:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Calculated height is ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('Used height is ' + str(height))
            col_change = False
        return height, col_change

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        if not ctx.is_simulating():
            if tip_track['counts'][pip] == tip_track['maxes'][pip]:
                ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \
                resuming.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0
        pip.pick_up_tip()

    ####################################
    # load labware and modules

    ####################################
    # Load Sample racks
    if NUM_SAMPLES < 96:
        rack_num = math.ceil(NUM_SAMPLES / 24)
        ctx.comment('Used source racks are ' + str(rack_num))
        samples_last_rack = NUM_SAMPLES - rack_num * 24
    else:
        rack_num = 4

    source_tube_types={'Screwcap 2ml': ['opentrons_24_tuberack_generic_2ml_screwcap','source tuberack with screwcap'],
                        'Eppendorf 1.5ml': ['opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap','source tuberack with eppendorf'],
                        }

    source_racks = [ctx.load_labware(
        source_tube_types[source_type][0], slot,
        source_tube_types[source_type][1] + str(i + 1)) for i, slot in enumerate(['4', '1', '6', '3'][:rack_num])
    ]

    ic_source_rack = ctx.load_labware('opentrons_24_tuberack_generic_2ml_screwcap', '9',
    'Internal control source')

    ic_source = ic_source_rack.wells()[0] # internal control comes from 1 bottle

    ##################################
    # Destination plate
    dest_plate = ctx.load_labware(
        'kf_96_wellplate_2400ul', '5', 'KF 96well destination plate')

    ####################################
    # Load tip_racks
    # tips20 = [ctx.load_labware('opentrons_96_filtertiprack_20ul', slot, '20µl filter tiprack')
    # for slot in ['2', '8']]
    tips300 = [ctx.load_labware('opentrons_96_filtertiprack_200ul', slot, '200µl filter tiprack')
                for slot in ['10', '11']]
    tips20 = [ctx.load_labware('opentrons_96_filtertiprack_20ul', slot, '20µl filter tiprack')
                for slot in ['8']]

    ################################################################################
    # Declare which reagents are in each reservoir as well as deepwell and elution plate

    # setup samples and destinations
    sample_sources_full = generate_source_table(source_racks)
    sample_sources = sample_sources_full[:NUM_SAMPLES]
    destinations = dest_plate.wells()[:NUM_SAMPLES]

    p20 = ctx.load_instrument(
        'p20_single_gen2', mount='left', tip_racks=tips20)

    p300 = ctx.load_instrument(
        'p300_single_gen2', mount='right', tip_racks=tips300)  # load P1000 pipette

    # used tip counter and set maximum tips available
    tip_track = {
        'counts': {p300: 0, p20: 0},  # p1000: 0},
        'maxes': {p300: len(tips300) * 96, p20: len(tips20)*96}  # ,p20: len(tips20)*96,
    }

    ############################################################################
    # STEP 1: Add Samples
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        # Transfer parameters
        start = datetime.now()
        for s, d in zip(sample_sources, destinations):
            if not p300.hw_pipette['has_tip']:
                pick_up(p300)
            # Mix the sample BEFORE dispensing
            #custom_mix(p1000, reagent = Samples, location = s, vol = volume_sample, rounds = 2, blow_out = True, mix_height = 15)
            move_vol_multichannel(p300, reagent = Samples, source = s, dest = d,
            vol=volume_sample, air_gap_vol = air_gap_vol, x_offset = x_offset,
                               pickup_height = 0.3, rinse = Samples.rinse, disp_height = -10,
                               blow_out = True, touch_tip = True)
            # Mix the sample AFTER dispensing
            #custom_mix(p300, reagent = Samples, location = d, vol = volume_sample, rounds = 2, blow_out = True, mix_height = 15)
            # Drop tip and update counter
            p300.drop_tip(home_after=False)
            tip_track['counts'][p300] += 1

        # Time statistics
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 2: Add internal control
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        #BEWARE, everything with the same tip, dispensed from top!

        # Transfer parameters
        start = datetime.now()
        for d in destinations:
            if not p20.hw_pipette['has_tip']:
                pick_up(p20)
            # Mix the sample BEFORE dispensing
            #custom_mix(p1000, reagent = Samples, location = s, vol = volume_sample, rounds = 2, blow_out = True, mix_height = 15)
            move_vol_multichannel(p20, reagent = IC, source = ic_source, dest = d,
                                  vol = ic_volume, air_gap_vol = air_gap_vol, x_offset = x_offset,
                                  pickup_height = 0.4, rinse = IC.rinse, disp_height = -8,
                                  blow_out = True, touch_tip = False)
            # Mix the sample AFTER dispensing
            #custom_mix(p20, reagent = Samples, location = d, vol = 10, rounds = 2,
            #blow_out = True, mix_height = 2, x_offset = x_offset)
            # Drop tip and update counter
        p20.drop_tip()
        tip_track['counts'][p20] += 1

        # Time statistics
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################

    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write('STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    # Light flash end of program

    '''if not ctx.is_simulating():
        os.system('mpg123 -f -8000 /etc/audio/speaker-test.mp3 &')'''

    for i in range(3):
        ctx._hw_manager.hardware.set_lights(rails=False)

        time.sleep(0.3)
        ctx._hw_manager.hardware.set_lights(rails=True)

        time.sleep(0.3)
        ctx._hw_manager.hardware.set_lights(rails=False)
    ctx.home()

    ctx.comment(
        'Finished! \nMove deepwell plate to Station B.')

    ctx.comment('Used 200µl tips in total: ' + str(tip_track['counts'][p300]))
    ctx.comment('Used 200ul racks in total: '+str(tip_track['counts'][p300] / 96))

    ctx.comment('Used p20 tips in total: ' + str(tip_track['counts'][p20]))
    ctx.comment('Used p20 racks in total: ' + str(tip_track['counts'][p20] / 96))
def run(ctx: protocol_api.ProtocolContext):
    w1_tip_pos_list             = []
    w2_tip_pos_list             = []
    elution_tip_pos_list        = []

    STEP = 0
    STEPS = { #Dictionary with STEP activation, description, and times
            1:{'Execute': True, 'description': 'Transferir bolas magnéticas'},
            2:{'Execute': True, 'description': 'Incubación con el imán ON', 'wait_time': 600},
            3:{'Execute': True, 'description': 'Desechar sobrenadante'},
            4:{'Execute': True, 'description': 'Imán OFF'},
            5:{'Execute': True, 'description': 'Transferir primer lavado'},
            6:{'Execute': True, 'description': 'Incubación con el imán ON', 'wait_time': 300},
            7:{'Execute': True, 'description': 'Desechar sobrenadante'},
            8:{'Execute': True, 'description': 'Imán OFF'},
            9:{'Execute': True, 'description': 'Transferir segundo lavado'},
            10:{'Execute': True, 'description': 'Incubación con el imán ON', 'wait_time': 300},
            11:{'Execute': True, 'description': 'Desechar sobrenadante'},
            12:{'Execute': True, 'description': 'Secado', 'wait_time': 300},
            13:{'Execute': True, 'description': 'Imán OFF'},
            14:{'Execute': True, 'description': 'Transferir elución'},
            15:{'Execute': True, 'description': 'Incubación con el imán ON', 'wait_time': 300},
            16:{'Execute': True, 'description': 'Transferir elución a la placa'},
            }

    #Folder and file_path for log time
    import os
    folder_path = '/var/lib/jupyter/notebooks/' + run_id
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/time_log.txt'

    #Define Reagents as objects with their properties
    class Reagent:
        def calc_vol_well(self):
            if(self.name == 'Sample'):
                self.num_wells = num_cols
                return VOLUME_SAMPLE
            elif self.placed_in_multi:
                trips = math.ceil(self.reagent_volume / self.max_volume_allowed)
                vol_trip = self.reagent_volume / trips * 8
                max_trips_well = math.floor(18000 / vol_trip)
                total_trips = num_cols * trips
                self.num_wells = math.ceil(total_trips / max_trips_well)
                return math.ceil(total_trips / self.num_wells) * vol_trip + self.dead_vol
            else:
                self.num_wells = 1
                return self.reagent_volume * NUM_SAMPLES

        def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, flow_rate_aspirate_mix, flow_rate_dispense_mix,
        air_gap_vol_bottom, air_gap_vol_top, disposal_volume, reagent_volume, v_fondo, max_volume_allowed = pipette_allowed_capacity,
        dead_vol = 1400, first_well = None, placed_in_multi = False):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.flow_rate_aspirate_mix = flow_rate_aspirate_mix
            self.flow_rate_dispense_mix = flow_rate_dispense_mix
            self.air_gap_vol_bottom = air_gap_vol_bottom
            self.air_gap_vol_top = air_gap_vol_top
            self.disposal_volume = disposal_volume
            self.max_volume_allowed = max_volume_allowed
            self.reagent_volume = reagent_volume
            self.col = 0
            self.vol_well = 0
            self.v_cono = v_fondo
            self.dead_vol = dead_vol
            self.first_well = first_well
            self.placed_in_multi = placed_in_multi
            self.vol_well_original = self.calc_vol_well() if reagent_volume * NUM_SAMPLES > 0 else 0
            self.vol_well = self.vol_well_original

    #Reagents and their characteristics
    Beads = Reagent(name = 'Beads', 
                    flow_rate_aspirate = 25,
                    flow_rate_dispense = 100,
                    flow_rate_aspirate_mix = 25,
                    flow_rate_dispense_mix = 100,
                    air_gap_vol_bottom = 5,
                    air_gap_vol_top = 0,
                    disposal_volume = 1,
                    reagent_volume = BEADS_VOLUME_PER_SAMPLE,
                    placed_in_multi = True,
                    dead_vol = 2000,
                    v_fondo = 695) #1.95 * multi_well_rack_area / 2, #Prismatic

    Wash_1 = Reagent(name = 'Wash 1',
                    flow_rate_aspirate = 25,
                    flow_rate_dispense = 100,
                    flow_rate_aspirate_mix = 25,
                    flow_rate_dispense_mix = 100,
                    air_gap_vol_bottom = 5,
                    air_gap_vol_top = 0,
                    disposal_volume = 1,
                    reagent_volume = WASH_1_VOLUME_PER_SAMPLE,
                    placed_in_multi = True,
                    v_fondo = 695) #1.95 * multi_well_rack_area / 2, #Prismatic)

    Wash_2 = Reagent(name = 'Wash 2',
                    flow_rate_aspirate = 25,
                    flow_rate_dispense = 100,
                    flow_rate_aspirate_mix = 25,
                    flow_rate_dispense_mix = 100,
                    air_gap_vol_bottom = 5,
                    air_gap_vol_top = 0,
                    disposal_volume = 1,
                    reagent_volume = WASH_2_VOLUME_PER_SAMPLE,
                    placed_in_multi = True,
                    v_fondo = 695) #1.95 * multi_well_rack_area / 2, #Prismatic)

    Elution = Reagent(name = 'Elution',
                    flow_rate_aspirate = 25,
                    flow_rate_dispense = 100,
                    flow_rate_aspirate_mix = 25,
                    flow_rate_dispense_mix = 100,
                    air_gap_vol_bottom = 5,
                    air_gap_vol_top = 0,
                    disposal_volume = 1,
                    reagent_volume = ELUTION_VOLUME_PER_SAMPLE,
                    placed_in_multi = True,
                    v_fondo = 695) #1.95*multi_well_rack_area/2) #Prismatic

    Sample = Reagent(name = 'Sample',
                    flow_rate_aspirate = 5, # Original 0.5
                    flow_rate_dispense = 100, # Original 1
                    flow_rate_aspirate_mix = 1,
                    flow_rate_dispense_mix = 1,
                    air_gap_vol_bottom = 5,
                    air_gap_vol_top = 0,
                    disposal_volume = 1,
                    reagent_volume = VOLUME_SAMPLE,
                    v_fondo = 4 * math.pi * 4**3 / 3) #Sphere

    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('VALORES DE VARIABLES')
    ctx.comment(' ')
    ctx.comment('Número de muestras: ' + str(NUM_SAMPLES) + ' (' + str(num_cols) + ' ' + ('columna' if num_cols == 1 else 'columnas') + ')')
    ctx.comment('Capacidad de puntas: ' + txt_tip_capacity)
    ctx.comment(' ')
    ctx.comment('Volumen de muestra en el deepwell: ' + str(VOLUME_SAMPLE) + ' ul')
    ctx.comment('Volumen de beads por muestra: ' + str(BEADS_VOLUME_PER_SAMPLE) + ' ul')
    ctx.comment('Volumen del lavado por muestra: ' + str(WASH_1_VOLUME_PER_SAMPLE) + ' ul')
    ctx.comment('Volumen del etanol por muestra: ' + str(WASH_2_VOLUME_PER_SAMPLE) + ' ul')
    ctx.comment('Volumen de elución por muestra: ' + str(ELUTION_VOLUME_PER_SAMPLE) + ' ul')
    ctx.comment('Volumen de elución a retirar del deepwell: ' + str(ELUTION_FINAL_VOLUME_PER_SAMPLE) + ' ul')
    ctx.comment(' ')
    ctx.comment('Número de mezclas en la primera recogida de un canal con bolas magnéticas: ' + str(BEADS_WELL_FIRST_TIME_NUM_MIXES))
    ctx.comment('Número de mezclas en el resto de recogidas de un canal con bolas magnéticas: ' + str(BEADS_WELL_NUM_MIXES)) 	
    ctx.comment('Número de mezclas con la solución de bolas magnéticas: ' + str(BEADS_NUM_MIXES))
    ctx.comment('Número de mezclas con el lavado: ' + str(WASH_1_NUM_MIXES))
    ctx.comment('Número de mezclas con el etanol lavado: ' + str(WASH_2_NUM_MIXES))
    ctx.comment('Número de mezclas con la elución: ' + str(ELUTION_NUM_MIXES))
    ctx.comment(' ')
    ctx.comment('Reciclado de puntas en los lavados activado: ' + str(TIP_RECYCLING_IN_WASH))
    ctx.comment('Reciclado de puntas en la elución activado: ' + str(TIP_RECYCLING_IN_ELUTION))
    ctx.comment(' ')
    ctx.comment('Activar módulo de temperatura: ' + str(SET_TEMP_ON))
    ctx.comment('Valor objetivo módulo de temepratura: ' + str(TEMPERATURE) + ' ºC')
    ctx.comment(' ')
    ctx.comment('Repeticiones del sonido final: ' + str(SOUND_NUM_PLAYS))
    ctx.comment('Foto-sensible: ' + str(PHOTOSENSITIVE))
    ctx.comment(' ')

    #########
    def str_rounded(num):
        return str(int(num + 0.5))

    ###################
    #Custom functions
    def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, offset, wait_time = 0, drop_height = -1, two_thirds_mix_bottom = False):
        '''
        Function for mix in the same location a certain number of rounds. Blow out optional. Offset
        can set to 0 or a higher/lower value which indicates the lateral movement
        '''
        if mix_height <= 0:
            mix_height = 1

        pipet.aspirate(1, location = location.bottom(z = mix_height), rate = reagent.flow_rate_aspirate_mix)

        for i in range(rounds):
            pipet.aspirate(vol, location = location.bottom(z = mix_height), rate = reagent.flow_rate_aspirate_mix)
            if two_thirds_mix_bottom and i < ((rounds / 3) * 2):
                pipet.dispense(vol, location = location.bottom(z = 5).move(Point(x = offset)), rate = reagent.flow_rate_dispense_mix)
            else:
                pipet.dispense(vol, location = location.top(z = drop_height).move(Point(x = offset)), rate = reagent.flow_rate_dispense_mix)
        
        pipet.dispense(1, location = location.bottom(z = mix_height), rate = reagent.flow_rate_dispense_mix)
        
        if blow_out == True:
            pipet.blow_out(location.top(z = -2)) # Blow out
        
        if wait_time != 0:
            ctx.delay(seconds = wait_time, msg = 'Esperando durante ' + str(wait_time) + ' segundos.')

    def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.4):
        nonlocal ctx
        ctx.comment('¿Volumen útil restante ' + str(reagent.vol_well - reagent.dead_vol) +
                    ' uL < volumen necesario ' + str(aspirate_volume - reagent.disposal_volume * 8) + ' uL?')
        if (reagent.vol_well - reagent.dead_vol + 1) < (aspirate_volume - reagent.disposal_volume * 8):
            ctx.comment('Se debe utilizar el siguiente canal')
            ctx.comment('Canal anterior: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment('Nuevo canal: ' + str(reagent.col))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('Nuevo volumen: ' + str(reagent.vol_well) + ' uL')
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area
            reagent.vol_well = reagent.vol_well - (aspirate_volume - reagent.disposal_volume * 8)
            ctx.comment('Volumen restante: ' + str(reagent.vol_well) + ' uL')
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area
            reagent.vol_well = reagent.vol_well - (aspirate_volume - (reagent.disposal_volume * 8))
            ctx.comment('La altura calculada es ' + str(round(height, 2)) + ' mm')
            if height < min_height:
                height = min_height
            ctx.comment('La altura utilizada es ' + str(round(height, 2)) + ' mm')
            col_change = False
        return height, col_change

    def move_vol_multi(pipet, reagent, source, dest, vol, x_offset_source, x_offset_dest, pickup_height,
        blow_out, wait_time = 0, touch_tip = False, touch_tip_v_offset = 0, drop_height = -5,
        aspirate_with_x_scroll = False, dispense_bottom_air_gap_before = False):

        # SOURCE
        if dispense_bottom_air_gap_before and reagent.air_gap_vol_bottom:
            pipet.dispense(reagent.air_gap_vol_bottom, source.top(z = -2), rate = reagent.flow_rate_dispense)

        if reagent.air_gap_vol_top != 0: #If there is air_gap_vol, switch pipette to slow speed
            pipet.air_gap(reagent.air_gap_vol_top, height = 0) #air gap

        if aspirate_with_x_scroll:
            aspirate_with_x_scrolling(pip = pipet, volume = vol, src = source, pickup_height = pickup_height, rate = reagent.flow_rate_aspirate, start_x_offset_src = 0, stop_x_offset_src = x_offset_source)
        else:
            s = source.bottom(pickup_height).move(Point(x = x_offset_source))
            pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate) # aspirate liquid

        if reagent.air_gap_vol_bottom != 0: #If there is air_gap_vol, switch pipette to slow speed
            pipet.air_gap(reagent.air_gap_vol_bottom, height = 0) #air gap

        # if wait_time != 0:
        #     ctx.delay(seconds=wait_time, msg='Esperando durante ' + str(wait_time) + ' segundos.')

        # GO TO DESTINATION
        d = dest.top(z = drop_height).move(Point(x = x_offset_dest))
        pipet.dispense(vol - reagent.disposal_volume + reagent.air_gap_vol_bottom, d, rate = reagent.flow_rate_dispense)

        if reagent.air_gap_vol_top != 0:
            pipet.dispense(reagent.air_gap_vol_top, dest.top(z = 0), rate = reagent.flow_rate_dispense)

        if blow_out == True:
            pipet.blow_out(dest.top(z = drop_height))

        if touch_tip == True:
            pipet.touch_tip(speed = 20, v_offset = touch_tip_v_offset, radius=0.7)

        if wait_time != 0:
            ctx.delay(seconds=wait_time, msg='Esperando durante ' + str(wait_time) + ' segundos.')

        #if reagent.air_gap_vol_bottom != 0:
            #pipet.move_to(dest.top(z = 0))
            #pipet.air_gap(reagent.air_gap_vol_bottom) #air gap
            #pipet.aspirate(air_gap_vol_bottom, dest.top(z = 0),rate = reagent.flow_rate_aspirate) #air gap

    def aspirate_with_x_scrolling(pip, volume, src, pickup_height = 0, rate = 1, start_x_offset_src = 0, stop_x_offset_src = 0):

        max_asp = volume/pip.min_volume
        inc_step = (start_x_offset_src - stop_x_offset_src) / max_asp

        for x in reversed(np.arange(stop_x_offset_src, start_x_offset_src, inc_step)):
            s = src.bottom(pickup_height).move(Point(x = x))
            pip.aspirate(volume = pip.min_volume, location = s, rate = rate)

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up_tip(pip, position = None):
        nonlocal tip_track
        #if not ctx.is_simulating():
        if recycle_tip:
            pip.pick_up_tip(tips300[0].wells()[0])
        else:
            if tip_track['counts'][pip] >= tip_track['maxes'][pip]:
                for i in range(3):
                    ctx._hw_manager.hardware.set_lights(rails=False)
                    ctx._hw_manager.hardware.set_lights(button=(1, 0 ,0))
                    time.sleep(0.3)
                    ctx._hw_manager.hardware.set_lights(rails=True)
                    ctx._hw_manager.hardware.set_lights(button=(0, 0 ,1))
                    time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button=(0, 1 ,0))
                ctx.pause('Reemplaza las cajas de puntas de ' + str(pip.max_volume) + 'µl antes de continuar.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0
                tip_track['num_refills'][pip] += 1
            if position is None:
                pip.pick_up_tip()
            else:
                pip.pick_up_tip(position)

    def drop_tip(pip, recycle = False):
        nonlocal tip_track
        #if not ctx.is_simulating():
        if recycle or recycle_tip:
            pip.return_tip()
        else:
            pip.drop_tip(home_after = False)
        if not recycle:
            tip_track['counts'][pip] += 8
            if not ctx.is_simulating() and not recycle_tip and tip_track['counts'][pip] % max_tips_in_trash == 0:
                play_sound('empty_trash_esp')


    def start_run():
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Empezando protocolo')
        if PHOTOSENSITIVE == False:
            ctx._hw_manager.hardware.set_lights(button = True, rails =  True)
        else:
            ctx._hw_manager.hardware.set_lights(button = True, rails =  False)
        now = datetime.now()

        # dd/mm/YY H:M:S
        start_time = now.strftime("%Y/%m/%d %H:%M:%S")
        return start_time

    def run_quiet_process(command):
        subprocess.check_output('{} &> /dev/null'.format(command), shell=True)

    def play_sound(filename):
        print('Speaker')
        print('Next\t--> CTRL-C')
        try:
            run_quiet_process('mpg123 {}'.format(path_sounds + filename + '.mp3'))
            run_quiet_process('mpg123 {}'.format(path_sounds + sonido_defecto))
            run_quiet_process('mpg123 {}'.format(path_sounds + filename + '.mp3'))
        except KeyboardInterrupt:
            pass
            print()

    def finish_run(switch_off_lights = False):
        ctx.comment('###############################################')
        ctx.comment('Protocolo finalizado')
        ctx.comment(' ')
        #Set light color to blue
        ctx._hw_manager.hardware.set_lights(button = True, rails =  False)
        now = datetime.now()
        # dd/mm/YY H:M:S
        finish_time = now.strftime("%Y/%m/%d %H:%M:%S")
        if PHOTOSENSITIVE==False:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button = False, rails =  False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button = True, rails =  True)
                time.sleep(0.3)
        else:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button = False, rails =  False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button = True, rails =  False)
                time.sleep(0.3)
        if switch_off_lights:
            ctx._hw_manager.hardware.set_lights(button = True, rails =  False)

        used_tips = tip_track['num_refills'][m300] * 96 * len(m300.tip_racks) + tip_track['counts'][m300]
        ctx.comment('Puntas de ' + txt_tip_capacity + ' utilizadas: ' + str(used_tips) + ' (' + str(round(used_tips / 96, 2)) + ' caja(s))')
        ctx.comment('###############################################')

        if not ctx.is_simulating():
            for i in range(SOUND_NUM_PLAYS):
                if i > 0:
                    time.sleep(60)
                play_sound('finished_process_esp')

        return finish_time

    def log_step_start():
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('PASO '+str(STEP)+': '+STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')
        return datetime.now()

    def log_step_end(start):
        end = datetime.now()
        time_taken = (end - start)
        STEPS[STEP]['Time:'] = str(time_taken)

        ctx.comment(' ')
        ctx.comment('Paso ' + str(STEP) + ': ' +STEPS[STEP]['description'] + ' hizo un tiempo de ' + str(time_taken))
        ctx.comment(' ')

    ##########
    def find_side(col):
        if col%2 == 0:
            side = -1 # left
        else:
            side = 1 # right
        return side


    def assign_wells(reagent, first_well_pos  = None):
        global next_well_index
        if first_well_pos is not None and first_well_pos > next_well_index:
            reagent.first_well = first_well_pos
        else:
            reagent.first_well = next_well_index + 1

        next_well_index = reagent.first_well - 1 + reagent.num_wells
        reagent.reagent_reservoir = reagent_res.rows()[0][reagent.first_well - 1:next_well_index]
        ctx.comment(reagent.name + ': ' + str(reagent.num_wells) +  (' canal' if reagent.num_wells == 1 else ' canales') + ' desde el canal '+ str(reagent.first_well) +' en el reservorio de 12 canales con un volumen de ' + str_rounded(reagent.vol_well_original) + ' uL cada uno')

####################################
    # load labware and modules
    ######## 12 well rack
    reagent_res = ctx.load_labware('usascientific_12_reservoir_22ml', '5','Reagent 12 Well Reservoir')

##################################
    ########## tempdeck
    tempdeck = ctx.load_module('Temperature Module Gen2', '1')

    ####### Elution plate - final plate, goes to C
    elution_plate = tempdeck.load_labware('kingfisher_96_aluminumblock_200ul', 'Kingfisher 96 Aluminum Block 200 uL')

############################################
    ######## Deepwell - comes from A
    magdeck = ctx.load_module('Magnetic Module Gen2', '4')
    deepwell_plate = magdeck.load_labware('kingfisher_96_wellplate_2000ul', 'KingFisher 96 Well Plate 2mL')

####################################
    ######## Waste reservoir
    waste_reservoir = ctx.load_labware('nest_1_reservoir_195ml', '7', 'waste reservoir') # Change to our waste reservoir
    waste = waste_reservoir.wells()[0] # referenced as reservoir

####################################
    ######### Load tip_racks
    tips300 = [ctx.load_labware('opentrons_96_tiprack_300ul', slot, txt_tip_capacity + ' filter tiprack')
        for slot in ['2', '3', '6', '8', '9', '10', '11']]

###############################################################################
    #Declare which reagents are in each reservoir as well as deepwell and elution plate
    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('VOLÚMENES PARA ' + str(NUM_SAMPLES) + ' MUESTRAS')
    ctx.comment(' ')

    assign_wells(Beads, 1)
    assign_wells(Wash_1, 5)
    assign_wells(Wash_2, 9)
    assign_wells(Elution, 12)

    ctx.comment('###############################################')
    ctx.comment(' ')

    work_destinations   = deepwell_plate.rows()[0][:Sample.num_wells]
    final_destinations  = elution_plate.rows()[0][:Sample.num_wells]

    # pipettes.
    m300 = ctx.load_instrument('p300_multi_gen2', 'right', tip_racks = tips300) # Load multi pipette

    #### used tip counter and set maximum tips available
    tip_track = {
        'counts': {m300: 0},
        'maxes': {m300: 96 * len(m300.tip_racks)}, #96 tips per tiprack * number or tipracks in the layout
        'num_refills' : {m300 : 0},
        'tips': { m300: [tip for rack in tips300 for tip in rack.rows()[0]]}
    }

###############################################################################
    start_run()
    magdeck.disengage()

    ###############################################################################
    # STEP 1 Transferir bolas magnéticas
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        beads_trips = math.ceil(Beads.reagent_volume / Beads.max_volume_allowed)
        beads_volume = Beads.reagent_volume / beads_trips
        beads_transfer_vol = []
        for i in range(beads_trips):
            beads_transfer_vol.append(beads_volume + Beads.disposal_volume)
        x_offset_source = 0
        x_offset_dest   = 0

        for i in range(num_cols):
            ctx.comment("Column: " + str(i))

            pick_up_tip(m300)

            for j,transfer_vol in enumerate(beads_transfer_vol):
                [pickup_height, change_col] = calc_height(Beads, multi_well_rack_area, transfer_vol * 8)    
                if change_col == True or (i == 0 and j == 0): #If we switch column because there is not enough volume left in current reservoir column we mix new column
                    ctx.comment('Mezclando nuevo canal del reservorio: ' + str(Beads.col + 1))
                    custom_mix(m300, Beads, Beads.reagent_reservoir[Beads.col],
                        vol = Beads.max_volume_allowed, rounds = BEADS_WELL_FIRST_TIME_NUM_MIXES, 
                        blow_out = False, mix_height = 1.5, offset = 0)
                    first_mix_done = True
                else:
                    ctx.comment('Mezclando canal del reservorio: ' + str(Beads.col + 1))
                    mix_height = 1.5 if pickup_height > 1.5 else pickup_height
                    custom_mix(m300, Beads, Beads.reagent_reservoir[Beads.col],
                        vol = Beads.max_volume_allowed, rounds = BEADS_WELL_NUM_MIXES, 
                        blow_out = False, mix_height = mix_height, offset = 0)

                ctx.comment('Aspirando desde la columna del reservorio: ' + str(Beads.col + 1))
                ctx.comment('La altura de recogida es ' + str(round(pickup_height, 2)) + ' mm')
                move_vol_multi(m300, reagent = Beads, source = Beads.reagent_reservoir[Beads.col],
                        dest = work_destinations[i], vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                        pickup_height = pickup_height, blow_out = True, drop_height = deepwell_top_drop_height)
            

            if BEADS_NUM_MIXES > 0:
                ctx.comment(' ')
                ctx.comment('Mezclando muestra ')
                mix_volume = min(Beads.max_volume_allowed, Sample.reagent_volume + Beads.reagent_volume)
                custom_mix(m300, Beads, location = work_destinations[i], vol = mix_volume, 
                        rounds = BEADS_NUM_MIXES, blow_out = False, mix_height = 1, offset = 0, wait_time = 2)

            m300.air_gap(Beads.air_gap_vol_bottom, height = 0) #air gap

            drop_tip(m300)

        log_step_end(start)
        ###############################################################################
        # STEP 1 Mezclar en deepwell
        ########

    ###############################################################################
    # STEP 2 Incubación con el imán ON
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        magdeck.engage(height = mag_height)
        ctx.delay(seconds = STEPS[STEP]['wait_time'], msg = 'Incubación con el imán ON durante ' + format(STEPS[STEP]['wait_time']) + ' segundos.')

        log_step_end(start)
        ####################################################################
        # STEP 2 Incubación con el imán ON
        ########

    ###############################################################################
    # STEP 3 Desechar sobrenadante
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        total_supernatant_volume = Sample.reagent_volume + Beads.reagent_volume

        supernatant_trips = math.ceil((total_supernatant_volume) / Sample.max_volume_allowed)
        supernatant_volume = Sample.max_volume_allowed # We try to remove an exceeding amount of supernatant to make sure it is empty
        supernatant_transfer_vol = []
        for i in range(supernatant_trips):
            supernatant_transfer_vol.append(supernatant_volume + Sample.disposal_volume)

        pickup_height = 0.5 # Original 0.5

        for i in range(num_cols):
            x_offset_source = find_side(i) * x_offset_rs_sn
            x_offset_dest   = 0

            if not m300.hw_pipette['has_tip']:
                pick_up_tip(m300)
            for j, transfer_vol in enumerate(supernatant_transfer_vol):
                ctx.comment('Aspirando de la columna del deepwell: ' + str(i+1))
                ctx.comment('La altura de recogida es ' + str(round(pickup_height, 2)) + ' mm')

                move_vol_multi(m300, reagent = Beads, source = work_destinations[i], dest = waste, vol = transfer_vol,
                        x_offset_source = x_offset_source, x_offset_dest = x_offset_dest, pickup_height = pickup_height,
                        wait_time = 2, blow_out = True, drop_height = waste_drop_height,
                        dispense_bottom_air_gap_before = not (i == 0 and j == 0))

                m300.air_gap(Sample.air_gap_vol_bottom, height = 0)

            drop_tip(m300)

        log_step_end(start)
        ###############################################################################
        # STEP 3 Desechar sobrenadante
        ########

    ###############################################################################
    # STEP 4 Imán OFF
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        # Imán OFF
        magdeck.disengage()

        log_step_end(start)
        ###############################################################################
        # STEP 4 Imán OFF
        ########

    ###############################################################################
    # STEP 5 Transferir primer lavado
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        wash_trips = math.ceil(Wash_1.reagent_volume / Wash_1.max_volume_allowed)
        wash_volume = Wash_1.reagent_volume / wash_trips #136.66
        wash_transfer_vol = []
        for i in range(wash_trips):
            wash_transfer_vol.append(wash_volume + Wash_1.disposal_volume)

        for i in range(num_cols):
            x_offset_source = 0
            x_offset_dest   = -1 * find_side(i) * x_offset_rs_mv
            if not m300.hw_pipette['has_tip']:
                pick_up_tip(m300)
                if TIP_RECYCLING_IN_WASH:
                    w1_tip_pos_list += [tip_track['tips'][m300][int((tip_track['counts'][m300] / 8) + i)]]
            for transfer_vol in wash_transfer_vol:
                [pickup_height, change_col] = calc_height(Wash_1, multi_well_rack_area, transfer_vol*8)
                ctx.comment('Aspirando desde la columna del reservorio: ' + str(Wash_1.first_well + Wash_1.col))
                ctx.comment('La altura de recogida es ' + str(round(pickup_height, 2)) + ' mm')

                move_vol_multi(m300, reagent = Wash_1, source = Wash_1.reagent_reservoir[Wash_1.col], dest = work_destinations[i],
                        vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                        pickup_height = pickup_height, drop_height = deepwell_top_drop_height, blow_out = False)

            if WASH_1_NUM_MIXES > 0:
                mix_volume = min(Wash_1.max_volume_allowed, Wash_1.reagent_volume)
                custom_mix(m300, Wash_1, location = work_destinations[i], vol = mix_volume, two_thirds_mix_bottom = True,
                        rounds = WASH_1_NUM_MIXES, blow_out = False, mix_height = 1.5, offset = x_offset_dest)

            m300.air_gap(Wash_1.air_gap_vol_bottom, height = 0) #air gap

            drop_tip(m300, recycle = TIP_RECYCLING_IN_WASH)

        log_step_end(start)
        ###############################################################################
        # STEP 5 Transferir primer lavado
        ########

    ###############################################################################
    # STEP 6 Incubación con el imán ON
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        magdeck.engage(mag_height)
        ctx.delay(seconds = STEPS[STEP]['wait_time'], msg = 'Incubación con el imán ON durante ' + format(STEPS[STEP]['wait_time']) + ' segundos.')

        log_step_end(start)
        ####################################################################
        # STEP 6 Incubación con el imán ON
        ########

    ###############################################################################
    # STEP 7 Desechar sobrenadante
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        supernatant_trips = math.ceil(Wash_1.reagent_volume / Wash_1.max_volume_allowed)
        supernatant_volume = Wash_1.max_volume_allowed # We try to remove an exceeding amount of supernatant to make sure it is empty
        supernatant_transfer_vol = []
        for i in range(supernatant_trips):
            supernatant_transfer_vol.append(supernatant_volume + Sample.disposal_volume)

        pickup_height = 0.5 # Original 0.5

        for i in range(num_cols):
            x_offset_source = find_side(i) * x_offset_rs_sn
            x_offset_dest   = 0

            if not m300.hw_pipette['has_tip']:
                if TIP_RECYCLING_IN_WASH:
                    pick_up_tip(m300, w1_tip_pos_list[i])
                    m300.dispense(Wash_1.air_gap_vol_top, work_destinations[i].top(z = 0), rate = Wash_1.flow_rate_dispense)
                else:
                    pick_up_tip(m300)
            for j, transfer_vol in enumerate(supernatant_transfer_vol):
                ctx.comment('Aspirando de la columna del deepwell: ' + str(i+1))
                ctx.comment('La altura de recogida es ' + str(round(pickup_height, 2)) + ' mm')

                move_vol_multi(m300, reagent = Sample, source = work_destinations[i], dest = waste, vol = transfer_vol,
                        x_offset_source = x_offset_source, x_offset_dest = x_offset_dest, pickup_height = pickup_height,
                        wait_time = 2, blow_out = False, drop_height = waste_drop_height,
                        dispense_bottom_air_gap_before = not (i == 0 and j == 0))

                m300.air_gap(Sample.air_gap_vol_bottom, height = 0)

            drop_tip(m300)

        log_step_end(start)
        ###############################################################################
        # STEP 7 Desechar sobrenadante
        ########

    ###############################################################################
    # STEP 8 Imán OFF
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        # Imán OFF
        magdeck.disengage()

        log_step_end(start)
        ###############################################################################
        # STEP 8 Imán OFF
        ########

    ###############################################################################
    # STEP 9 Transferir segundo lavado
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        wash_trips = math.ceil(Wash_2.reagent_volume / Wash_2.max_volume_allowed)
        wash_volume = Wash_2.reagent_volume / wash_trips #136.66
        wash_transfer_vol = []
        for i in range(wash_trips):
            wash_transfer_vol.append(wash_volume + Wash_2.disposal_volume)
        pickup_height = 0.5

        for i in range(num_cols):
            x_offset_source = 0
            x_offset_dest   = -1 * find_side(i) * x_offset_rs_mv
            if not m300.hw_pipette['has_tip']:
                pick_up_tip(m300)
                if TIP_RECYCLING_IN_WASH:
                    w2_tip_pos_list += [tip_track['tips'][m300][int((tip_track['counts'][m300] / 8) + i)]]
            for transfer_vol in wash_transfer_vol:
                [pickup_height, change_col] = calc_height(Wash_2, multi_well_rack_area, transfer_vol*8)
                ctx.comment('Aspirando desde la columna del reservorio: ' + str(Wash_2.first_well + Wash_2.col))
                ctx.comment('La altura de recogida es ' + str(round(pickup_height, 2)) + ' mm')

                move_vol_multi(m300, reagent = Wash_2, source = Wash_2.reagent_reservoir[Wash_2.col], dest = work_destinations[i],
                        vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                        pickup_height = pickup_height, drop_height = deepwell_top_drop_height, blow_out = False)

            if WASH_2_NUM_MIXES > 0:
                mix_volume = min(Wash_2.max_volume_allowed, Wash_2.reagent_volume)
                custom_mix(m300, Wash_2, location = work_destinations[i], vol = mix_volume, two_thirds_mix_bottom = True,
                        rounds = WASH_2_NUM_MIXES, blow_out = False, mix_height = 1.5, offset = x_offset_dest)

            m300.air_gap(Wash_2.air_gap_vol_bottom, height = 0) #air gap

            drop_tip(m300, recycle = TIP_RECYCLING_IN_WASH)

        log_step_end(start)
        ###############################################################################
        # STEP 9 Transferir segundo lavado
        ########

    ###############################################################################
    # STEP 10 Incubación con el imán ON
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        magdeck.engage(mag_height)
        ctx.delay(seconds = STEPS[STEP]['wait_time'], msg = 'Incubación con el imán ON durante ' + format(STEPS[STEP]['wait_time']) + ' segundos.')

        log_step_end(start)
        ####################################################################
        # STEP 10 Incubación con el imán ON
        ########

    ###############################################################################
    # STEP 11 Desechar sobrenadante
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        supernatant_trips = math.ceil(Wash_2.reagent_volume / Wash_2.max_volume_allowed)
        supernatant_volume = Wash_2.max_volume_allowed # We try to remove an exceeding amount of supernatant to make sure it is empty
        supernatant_transfer_vol = []
        for i in range(supernatant_trips):
            supernatant_transfer_vol.append(supernatant_volume + Sample.disposal_volume)

        pickup_height = 0.5 # Original 0.5

        for i in range(num_cols):
            x_offset_source = find_side(i) * x_offset_rs_sn
            x_offset_dest   = 0

            if not m300.hw_pipette['has_tip']:
                if TIP_RECYCLING_IN_WASH:
                    pick_up_tip(m300, w2_tip_pos_list[i])
                    m300.dispense(Wash_2.air_gap_vol_top, work_destinations[i].top(z = 0), rate = Wash_2.flow_rate_dispense)
                else:
                    pick_up_tip(m300)
            for j, transfer_vol in enumerate(supernatant_transfer_vol):
                ctx.comment('Aspirando de la columna del deepwell: ' + str(i+1))
                ctx.comment('La altura de recogida es ' + str(round(pickup_height, 2)) + ' mm')

                move_vol_multi(m300, reagent = Sample, source = work_destinations[i], dest = waste, vol = transfer_vol,
                        x_offset_source = x_offset_source, x_offset_dest = x_offset_dest, pickup_height = pickup_height,
                        wait_time = 2, blow_out = False, dispense_bottom_air_gap_before = not (i == 0 and j == 0),
                        drop_height = waste_drop_height)

                m300.air_gap(Sample.air_gap_vol_bottom, height = 0)

            drop_tip(m300)

        log_step_end(start)
        ###############################################################################
        # STEP 11 Desechar sobrenadante
        ########

    ###############################################################################
    # STEP 12 Secado
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        ctx.comment(' ')
        ctx.delay(seconds=STEPS[STEP]['wait_time'], msg='Secado durante ' + format(STEPS[STEP]['wait_time']) + ' segundos.') #
        ctx.comment(' ')

        log_step_end(start)
        ###############################################################################
        # STEP 12 Secado
        ########

    ###############################################################################
    # STEP 13 Imán OFF
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        # Imán OFF
        magdeck.disengage()

        log_step_end(start)
        ###############################################################################
        # STEP 13 Imán OFF
        ########

    ###############################################################################
    # STEP 14 Transferir elución
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        elution_trips = math.ceil(Elution.reagent_volume / Elution.max_volume_allowed)
        elution_volume = Elution.reagent_volume / elution_trips
        elution_wash_vol = []
        for i in range(elution_trips):
            elution_wash_vol.append(elution_volume + Sample.disposal_volume)

        ########
        # Water or elution buffer
        for i in range(num_cols):
            x_offset_source = 0
            x_offset_dest   = -1 * find_side(i) * x_offset_rs_mv # Original 0
            if not m300.hw_pipette['has_tip']:
                pick_up_tip(m300)
                if TIP_RECYCLING_IN_ELUTION:
                    elution_tip_pos_list += [tip_track['tips'][m300][int((tip_track['counts'][m300] / 8) + i)]]
            for transfer_vol in elution_wash_vol:
                #Calculate pickup_height based on remaining volume and shape of container
                [pickup_height, change_col] = calc_height(Elution, multi_well_rack_area, transfer_vol*8)
                ctx.comment('Aspirando desde la columna del reservorio: ' + str(Elution.first_well + Elution.col))
                ctx.comment('La altura de recogida es ' + str(round(pickup_height, 2)) + ' mm')

                move_vol_multi(m300, reagent = Elution, source = Elution.reagent_reservoir[Elution.col], dest = work_destinations[i],
                        vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                        pickup_height = pickup_height, blow_out = False, drop_height = -35)

            if ELUTION_NUM_MIXES > 0:
                ctx.comment(' ')
                ctx.comment('Mezclando muestra con Elution')
                mix_volume = min(Elution.max_volume_allowed, Elution.reagent_volume)
                custom_mix(m300, Elution, work_destinations[i], vol = mix_volume, rounds = ELUTION_NUM_MIXES,
                        blow_out = False, mix_height = 1, offset = x_offset_dest, drop_height = -35)

            m300.air_gap(Elution.air_gap_vol_bottom, height = 0) #air gap

            drop_tip(m300, recycle = TIP_RECYCLING_IN_ELUTION)

        log_step_end(start)
        ###############################################################################
        # STEP 14 Transferir elución
        ########

    ###############################################################################
    # STEP 15 Incubación con el imán ON
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        magdeck.engage(mag_height)
        ctx.delay(seconds = STEPS[STEP]['wait_time'], msg = 'Incubación con el imán ON durante ' + format(STEPS[STEP]['wait_time']) + ' segundos.')

        log_step_end(start)
        ####################################################################
        # STEP 15 Incubación con el imán ON
        ########

    ###############################################################################
    # STEP 16 Transferir elución a la placa
    ########
    STEP += 1
    if STEPS[STEP]['Execute']==True:
        start = log_step_start()

        elution_trips = math.ceil(ELUTION_FINAL_VOLUME_PER_SAMPLE / Elution.max_volume_allowed)
        elution_volume = ELUTION_FINAL_VOLUME_PER_SAMPLE / elution_trips
        elution_vol = []
        for i in range(elution_trips):
            elution_vol.append(elution_volume + Elution.disposal_volume)

        for i in range(num_cols):
            x_offset_source = find_side(i) * x_offset_rs_sn
            x_offset_dest   = 0
            if not m300.hw_pipette['has_tip']:
                if TIP_RECYCLING_IN_ELUTION:
                    pick_up_tip(m300, elution_tip_pos_list[i])
                    m300.dispense(Elution.air_gap_vol_top, work_destinations[i].top(z = 0), rate = Elution.flow_rate_dispense)
                else:
                    pick_up_tip(m300)
            for transfer_vol in elution_vol:
                #Pickup_height is fixed here
                pickup_height = 1
                ctx.comment('Aspirando de la columna del deepwell: ' + str(i+1))
                ctx.comment('La altura de recogida es ' + str(round(pickup_height, 2)) + ' mm' )

                move_vol_multi(m300, reagent = Sample, source = work_destinations[i], dest = final_destinations[i],
                        vol = transfer_vol, x_offset_source = x_offset_source, x_offset_dest = x_offset_dest,
                        pickup_height = pickup_height, blow_out = True, touch_tip = False, drop_height = -1)

            m300.air_gap(Sample.air_gap_vol_bottom, height = 0) #air gap

            drop_tip(m300)

        if SET_TEMP_ON == True:
            tempdeck.set_temperature(TEMPERATURE)

        log_step_end(start)
        ###############################################################################
        # STEP 16 Transferir elución a la placa
        ########


    magdeck.disengage()
    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('Homing robot')
    ctx.comment('###############################################')
    ctx.comment(' ')
    ctx.home()
###############################################################################
    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write('STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    finish_run(switch_off_lights)