diff --git a/frgpascal/hardware/calibrations/Hotplate1_calibration.yaml b/frgpascal/hardware/calibrations/Hotplate1_calibration.yaml index beea195c..91659d3c 100644 --- a/frgpascal/hardware/calibrations/Hotplate1_calibration.yaml +++ b/frgpascal/hardware/calibrations/Hotplate1_calibration.yaml @@ -1,16 +1,16 @@ p0: - - 214.4 - - 89.3 - - 52.5 + - 73.3 + - 52.3 - - 274.4 - - 89.3 - - 52.5 + - 73.3 + - 52.3 - - 274.4 - - 149.3 - - 52.6 + - 133.3 + - 52.3 - - 214.4 - - 149.3 - - 52.5 + - 133.3 + - 52.3 p1: - - 0.0 - 0.0 diff --git a/frgpascal/hardware/calibrations/Hotplate2_calibration.yaml b/frgpascal/hardware/calibrations/Hotplate2_calibration.yaml index 81b6a1e1..3c14084c 100644 --- a/frgpascal/hardware/calibrations/Hotplate2_calibration.yaml +++ b/frgpascal/hardware/calibrations/Hotplate2_calibration.yaml @@ -1,16 +1,16 @@ p0: - - 214.2 - 216.3 - - 51.4 + - 51.2 - - 274.2 - 216.3 - - 51.7 + - 51.2 - - 274.2 - 276.3 - - 51.9 + - 51.2 - - 214.2 - 276.3 - - 51.7 + - 51.2 p1: - - 0.0 - 0.0 diff --git a/frgpascal/hardware/calibrations/Tray1_calibration.yaml b/frgpascal/hardware/calibrations/Tray1_calibration.yaml index 0d2aa4b7..298fdccf 100644 --- a/frgpascal/hardware/calibrations/Tray1_calibration.yaml +++ b/frgpascal/hardware/calibrations/Tray1_calibration.yaml @@ -1,16 +1,16 @@ p0: -- - 492.0 - - 35.5 - - 57.2 -- - 492.0 - - 160.7 - - 57.2 -- - 546.0 - - 160.7 - - 57.2 -- - 546.0 - - 35.5 - - 57.2 +- - 494.2 + - 18.3 + - 57.5 +- - 494.2 + - 143.5 + - 57.5 +- - 548.2 + - 143.5 + - 57.5 +- - 548.2 + - 18.3 + - 57.5 p1: - - 0.0 - 0.0 diff --git a/frgpascal/hardware/calibrations/Tray2_calibration.yaml b/frgpascal/hardware/calibrations/Tray2_calibration.yaml index 59190c17..2e38b7ea 100644 --- a/frgpascal/hardware/calibrations/Tray2_calibration.yaml +++ b/frgpascal/hardware/calibrations/Tray2_calibration.yaml @@ -1,16 +1,16 @@ p0: -- - 405.1 - - 35.5 - - 57.1 -- - 405.1 - - 160.7 - - 57.1 -- - 459.1 - - 160.7 - - 57.1 -- - 459.1 - - 35.5 - - 57.1 +- - 407.3 + - 19.9 + - 57.6 +- - 407.3 + - 145.1 + - 57.6 +- - 461.3 + - 145.1 + - 57.6 +- - 461.3 + - 19.9 + - 57.6 p1: - - 0.0 - 0.0 diff --git a/frgpascal/hardware/calibrations/characterizationaxis_calibration.yaml b/frgpascal/hardware/calibrations/characterizationaxis_calibration.yaml index 62d421f2..d5ffcdad 100644 --- a/frgpascal/hardware/calibrations/characterizationaxis_calibration.yaml +++ b/frgpascal/hardware/calibrations/characterizationaxis_calibration.yaml @@ -1,3 +1,3 @@ -- 562.1 -- 264.1 -- 52.3 +- 563.3 +- 247.7 +- 52.7 diff --git a/frgpascal/hardware/calibrations/spincoater_calibration.yaml b/frgpascal/hardware/calibrations/spincoater_calibration.yaml index 954a10e7..5541cc48 100644 --- a/frgpascal/hardware/calibrations/spincoater_calibration.yaml +++ b/frgpascal/hardware/calibrations/spincoater_calibration.yaml @@ -1,3 +1,3 @@ -- 0.6 -- 28.7 -- 54.3 +- 2.0 +- 11.5 +- 54.8 diff --git a/frgpascal/hardware/hardwareconstants.yaml b/frgpascal/hardware/hardwareconstants.yaml index 35926747..6e907e3c 100644 --- a/frgpascal/hardware/hardwareconstants.yaml +++ b/frgpascal/hardware/hardwareconstants.yaml @@ -5,7 +5,7 @@ gantry: device_identifiers: vid: 7855 #vendor id, converted from hex to integer. WINDOWS ONLY can be determined by https://interworks.com/blog/ijahanshahi/2014/07/18/identify-vid-pid-usb-device/ pid: 4 #product id, converted from hex to integer. WINDOWS ONLY. see link above - location: "1-1.1.1.2" #WINDOWS + location: "1-10" #WINDOWS pollingrate: 0.05 #delay (seconds) between sending a command and reading a response timeout: 15 #max time (seconds) allotted to gantry motion before flagging a movement error @@ -102,6 +102,7 @@ liquidhandler: travel_slow: 4 #time to move if slow_travel active dispensedelay: 0.8 # time (seconds) between initiating a dispense command and the liquid beginning to hit the spincoating sample dispensedelay_slow: 1 #time to dispense *from staging position* if slow_travel active + droptipintotrash: 26 #time to drop a tip into the trash and reset the pipette to be ready for further instructions # dispense_delay: 1 # aspiration_delay: 22.5 # time (seconds) to perform an aspiration and stage the pipette # staging_delay: 1.5 # time (seconds) to move pipette into position for drop staging @@ -117,9 +118,9 @@ spincoater: # communication device_identifiers: #for the arduino controlling the vacuum solenoid # serialid: #LINUX ONLY "558383339323513140D1" #hardware ID of spincoater arduino. Used to find correct COM port - vid: 9025 #WINDOWS ONLY - pid: 67 #WINDOWS ONLY - serial_number: '55838333932351108212' + vid: 2341 #WINDOWS ONLY + pid: 0043 #WINDOWS ONLY + serial_number: '95032303737351B01170' switchindex: "vacuumsolenoid" #key for switchbox relay that controls the vacuum solenoid (look at line ~32 in frgpascal.hardware.switchbox) communication_interval: 0.1 #delay (seconds) between communication to the odrive # movement @@ -136,7 +137,7 @@ characterizationline: device_identifiers: vid: 7855 #WINDOWS ONLY pid: 4 #WINDOWS ONLY - location: "1-1.1.4" #WINDOWS + GANTRY ONLY + location: "1-1.3" #WINDOWS + GANTRY ONLY pollingrate: 0.05 timeout: 20 #max time (seconds) to allow movement before flagging an error positiontolerance: 0.05 #tolerance (mm) for movements to be considered complete diff --git a/frgpascal/hardware/liquidhandler.py b/frgpascal/hardware/liquidhandler.py index 29f35e39..45147fd3 100644 --- a/frgpascal/hardware/liquidhandler.py +++ b/frgpascal/hardware/liquidhandler.py @@ -17,11 +17,12 @@ tc = constants["timings"] -def expected_timings(drop): +def expected_timings(drop, drop_previous_tip=True): """Estimate the duration (seconds) liquid aspiration will require for a given drop Args: drop (dict): dictionary of drop parameters + drop_previous_tip (dict): whether the previous tip must be dropped prior to aspiration Returns: float: duration, in seconds @@ -33,11 +34,12 @@ def expected_timings(drop): ) # overhead time for aspirate+dispense cycles to mix solution prior to final aspiration if drop["touch_tip"]: aspirate_duration += ac["touchtip"] - if drop["slow_retract"]: - aspirate_duration += ac["slowretract"] - if drop["air_gap"]: - aspirate_duration += ac["airgap"] - + # if drop["slow_retract"]: + # aspirate_duration += ac["slowretract"] + # if drop["air_gap"]: + # aspirate_duration += ac["airgap"] + if drop_previous_tip: + aspirate_duration += tc["droptipintotrash"] if drop["slow_travel"]: staging_duration = tc["travel_slow"] dispense_duration = tc["dispensedelay_slow"] @@ -146,19 +148,13 @@ def stage_antisolvent( def clear_chuck(self, taskid=None, nist_time=None, **kwargs): taskid = self.server.add_to_queue( - task="clear_chuck", - taskid=taskid, - nist_time=nist_time, - **kwargs, + task="clear_chuck", taskid=taskid, nist_time=nist_time, **kwargs, ) return taskid def cleanup(self, taskid=None, nist_time=None, **kwargs): taskid = self.server.add_to_queue( - task="cleanup", - taskid=taskid, - nist_time=nist_time, - **kwargs, + task="cleanup", taskid=taskid, nist_time=nist_time, **kwargs, ) return taskid diff --git a/frgpascal/workers.py b/frgpascal/workers.py index 793a743d..e6bc469e 100644 --- a/frgpascal/workers.py +++ b/frgpascal/workers.py @@ -138,7 +138,10 @@ def future_callback(future): self.logger.info(f"executing {task_description} as thread") future = asyncio.gather( self.loop.run_in_executor( - self.maestro.threadpool, function, sample, details, + self.maestro.threadpool, + function, + sample, + details, ) ) future.add_done_callback(future_callback) @@ -537,9 +540,11 @@ def _expected_dispense_duration(self, drop) -> float: def _generatelhtasks_onedrop(self, t0, drop): liquidhandlertasks = {} - (aspirate_duration, staging_duration, dispense_duration,) = expected_timings( - drop - ) + ( + aspirate_duration, + staging_duration, + dispense_duration, + ) = expected_timings(drop) headstart = ( aspirate_duration + staging_duration + dispense_duration - drop["time"] @@ -582,40 +587,60 @@ def _generatelhtasks_onedrop(self, t0, drop): return headstart, liquidhandlertasks def _generatelhtasks_twodrops(self, t0, drop0, drop1): - aspirate0_duration, staging0_duration, dispense0_duration = expected_timings( drop0 ) aspirate1_duration, staging1_duration, dispense1_duration = expected_timings( - drop1 - ) + drop1, drop_previous_tip=True + ) # drop_previous_tip should usually be false, but for now we are assuming we only have one pipette tip to work with! + if (drop1["time"] - drop0["time"]) < ( aspirate1_duration + staging1_duration + dispense1_duration - ): # if the two drops are too close in time, let's aspirate them both at the beginning - return self._generatelhtasks_twodrops_together( - t0, - drop0, - drop1, - aspirate0_duration, - staging0_duration, - dispense0_duration, - aspirate1_duration, - staging1_duration, - dispense1_duration, + ): + raise ValueError( + "Drops are not spaced enough apart for the liquid handler to perform the spincoating process!" ) - else: - return self._generatelhtasks_twodrops_separate( - t0, - drop0, - drop1, - aspirate0_duration, - staging0_duration, - dispense0_duration, - aspirate1_duration, - staging1_duration, - dispense1_duration, - ) + return self._generatelhtasks_twodrops_onlyrightpipette( + t0, + drop0, + drop1, + aspirate0_duration, + staging0_duration, + dispense0_duration, + aspirate1_duration, + staging1_duration, + dispense1_duration, + ) + + ### TYPICAL LOGIC ASSUMING WE HAVE TWO PIPETTE TIPS TO WORK WITH + # if (drop1["time"] - drop0["time"]) < ( + # aspirate1_duration + staging1_duration + dispense1_duration + # ): # if the two drops are too close in time, let's aspirate them both at the beginning + # return self._generatelhtasks_twodrops_together( + # t0, + # drop0, + # drop1, + # aspirate0_duration, + # staging0_duration, + # dispense0_duration, + # aspirate1_duration, + # staging1_duration, + # dispense1_duration, + # ) + + # else: + # return self._generatelhtasks_twodrops_separate( + # t0, + # drop0, + # drop1, + # aspirate0_duration, + # staging0_duration, + # dispense0_duration, + # aspirate1_duration, + # staging1_duration, + # dispense1_duration, + # ) def _generatelhtasks_twodrops_together( self, @@ -826,6 +851,105 @@ def _generatelhtasks_twodrops_separate( return headstart, liquidhandlertasks + def _generatelhtasks_twodrops_onlyrightpipette( + self, + t0, + drop0, + drop1, + aspirate0_duration, + staging0_duration, + dispense0_duration, + aspirate1_duration, + staging1_duration, + dispense1_duration, + ): + """Aspirate, stage, and dispense first drop before aspirating the second drop.""" + liquidhandlertasks = {} + + # timings + headstart = ( + aspirate0_duration + staging0_duration + dispense0_duration - drop0["time"] + ) + headstart = max(headstart, 0) + aspirate0_time = ( + t0 + + drop0["time"] + + headstart + - aspirate0_duration + - staging0_duration + - dispense0_duration + ) + dispense0_time = t0 + drop0["time"] + headstart - dispense0_duration + aspirate1_time = ( + t0 + + drop1["time"] + + headstart + - aspirate1_duration + - staging1_duration + - dispense1_duration + ) + dispense1_time = t0 + drop1["time"] + headstart - dispense1_duration + + # build tasklist + liquidhandlertasks[ + "aspirate_solution0" + ] = self.liquidhandler.aspirate_for_spincoating( + nist_time=aspirate0_time, + tray=drop0["solution"]["well"]["labware"], + well=drop0["solution"]["well"]["well"], + volume=drop0["volume"], + pipette="perovskite", + slow_retract=drop0["slow_retract"], + air_gap=drop0["air_gap"], + touch_tip=drop0["touch_tip"], + pre_mix=drop0["pre_mix"], + reuse_tip=drop0["reuse_tip"], + ) + liquidhandlertasks["stage_solution0"] = self.liquidhandler.stage_perovskite( + nist_time=aspirate0_time + 0.1, # immediately after aspiration + slow_travel=drop0["slow_travel"], + ) + liquidhandlertasks["dispense_solution0"] = self.liquidhandler.drop_perovskite( + nist_time=dispense0_time, + height=drop0["height"], + rate=drop0["rate"], + slow_travel=drop0["slow_travel"], + ) + + liquidhandlertasks["drop_tip_from_dispense0"] = self.liquidhandler.cleanup( + nist_time=dispense0_time + 0.1 + ) + + liquidhandlertasks[ + "aspirate_solution1" + ] = self.liquidhandler.aspirate_for_spincoating( + nist_time=aspirate1_time, + tray=drop1["solution"]["well"]["labware"], + well=drop1["solution"]["well"]["well"], + volume=drop1["volume"], + pipette="perovskite", + slow_retract=drop1["slow_retract"], + air_gap=drop1["air_gap"], + touch_tip=drop1["touch_tip"], + pre_mix=drop1["pre_mix"], + reuse_tip=drop1["reuse_tip"], + ) + liquidhandlertasks["stage_solution1"] = self.liquidhandler.stage_perovskite( + nist_time=aspirate1_time + 0.1, # immediately after aspiration + slow_travel=drop1["slow_travel"], + ) + + liquidhandlertasks["dispense_solution1"] = self.liquidhandler.drop_perovskite( + nist_time=dispense1_time, + height=drop1["height"], + rate=drop1["rate"], + slow_travel=drop1["slow_travel"], + ) + + self.liquidhandler.cleanup(nist_time=dispense1_time + 0.5) + + return headstart, liquidhandlertasks + def spincoat(self, sample, details): """executes a series of spin coating steps. A final "stop" step is inserted at the end to bring the rotor to a halt.