def main(args, stdin, stdout, stderr): "Wait for a minimum time or linearity, then run P gradient, writing real-time data." ## run the experiment # open output file, do not overwrite! with open(args['file_log'], 'x') as hand_log: # compose and write header list_head = ["clock", "watch", "state"] + list(states.head()) + ["T_int", "T_ext", "T_act", "P_act", "intensity"] line_head = "\t".join(list_head) hand_log.write(line_head + '\n') hand_log.flush() # now we're opening serial connections, which need to be closed cleanly on exit try: # init instruments print("connecting...", file=stderr) bath = isotemp6200.IsotempController(port=args['port_bath']) print("temperature controller √", file=stderr) pump = isco260D.ISCOController(port=args['port_pump']) print("pressure controller √", file=stderr) spec = rf5301.RF5301(port=args['port_spec']) print("fluorospectrometer √", file=stderr) ## hardware init print("initializing...", file=stderr) # start bath circulator print("bath", end=' ', file=stderr) # set precision prec_bath = 2 # number of decimal places while not bath.temp_prec(prec_bath): pass while not bath.on(): bath.on(True) print('√', file=stderr) # clear pump and write gradient program print("pump", end=' ', file=stderr) while not pump.remote(): pass while not pump.clear(): pass # write gradient # for now, gradient should be programmed at pump controller # get initial volume vol_start = False while not vol_start: vol_start = pump.vol_get() print("√ V0 = {} mL".format(vol_start), file=stderr) # open the shutter, unless in auto if not args["auto_shut"]: while not spec.shutter(True): pass # declare async queues queue_bath = deque(maxlen=1) queue_pump = deque(maxlen=1) queue_spec = deque(maxlen=1) # start polling threads # all device instances have RLocks! bath_free = threading.Event() pump_free = threading.Event() spec_free = threading.Event() [event.set() for event in (bath_free, pump_free, spec_free)] threading.Thread(name="pollbath", target=poll, args=(bath, bath_free, queue_bath)).start() threading.Thread(name="pollpump", target=poll, args=(pump, pump_free, queue_pump)).start() threading.Thread(name="pollspec", target=poll, args=(spec, spec_free, queue_spec)).start() ## run experiment # start experiment timer (i.e. stopwatch) time_start = time.time() time_air_tot = 0 save_air = True list_data = [0]*10 # iterate over test states for state_num in range(states.shape[0]): # make dicts for this state, the last, and the next # takes about 1 ms state_curr = states.iloc[state_num+0].to_dict() if state_num: state_prev = states.iloc[state_num-1].to_dict() else: # if first state state_prev = {key: 0 for key in state_curr.keys()} chg_prev = {key: True for key in state_curr.keys()} if state_num < states.shape[0]-1: state_next = states.iloc[state_num+1].to_dict() else: # if final state state_next = {key: 0 for key in state_curr.keys()} chg_next = {key: True for key in state_curr.keys()} # which params have changed since previous state? chg_prev = {key: (state_curr[key] != state_prev[key]) for key in state_curr.keys()} chg_next = {key: (state_curr[key] != state_next[key]) for key in state_curr.keys()} time_state = time.time() # mark time waited = False # did the state have to wait for stability? readings = 0 # reset n counter # status update print("state {}/{}:".format(state_num+1, states.shape[0]), file=stderr) print(state_curr, file=stderr) # set temp persistently # this is the actual setpoint passed to the waterbath temp_set = round(temp_ext2int(temp_act2ext(state_curr['T_set'])), prec_bath) temp_tol = round(args['tol_T'] / 1.18052628 * 1.312841332, prec_bath) bath_free.clear() while not isclose(bath.temp_set(), temp_set): print("setting temperature to {}˚C".format(state_curr['T_set']), file=stderr, end=' ') if bath.temp_set(temp_set): print('√', file=stderr) bath_free.set() # set pres persistently pump_free.clear() while not isclose(pump.press_set(), state_curr['P_set']): print("setting pressure to {} bar".format(state_curr['P_set']), file=stderr, end=' ') if pump.press_set(state_curr['P_set']): print('√', file=stderr) pump_free.set() ## set the excitation wavelength #while not isclose(spec.ex_wl(), state_curr['wl_ex']): # print("setting excitation WL to {} nm".format(state_curr['wl_ex']), file=stderr, end=' ') # if spec.ex_wl(state_curr['wl_ex']): print('√', file=stderr) # ## set the excitation wavelength #while not isclose(spec.em_wl(), state_curr['wl_em']): # print("setting emission WL to {} nm".format(state_curr['wl_em']), file=stderr, end=' ') # if spec.ex_wl(state_curr['wl_em']): print('√', file=stderr) # temporary WL setters # Persistence implemented over cycles to improve efficiency; # note that this checks the previous data row. #NTS 20200719 reimplement list_data as a dict!!! if not ((state_curr['wl_ex'] == list_data[4]) and (state_curr['wl_em'] == list_data[5])): spec_free.clear() if (state_curr['wl_ex'] == 340) and (state_curr['wl_em'] == 440): print("setting wavelengths to Laurdan blue", file=stderr, end=' ') if spec.wl_set_laurdan_blu(): print('√', file=stderr) elif (state_curr['wl_ex'] == 340) and (state_curr['wl_em'] == 490): print("setting wavelengths to Laurdan red", file=stderr, end=' ') if spec.wl_set_laurdan_red(): print('√', file=stderr) spec_free.set() # init a log table for the state trails = pd.DataFrame(columns = list_head) # data logging loop data_dict = {} # init the data dict. Persistent the first time. for dq in (queue_bath, queue_pump, queue_spec): while True: # ensure that *something* gets popped out so the dict is complete try: data_dict.update(dq.popleft()) break except: pass while True: time_cycle = time.time() # DATA FIRST for dq in (queue_bath, queue_pump, queue_spec): try: data_dict.update(dq.popleft()) break except: pass # add internal data data_dict.update( { "clock" : time.strftime("%Y%m%d %H%M%S"), "watch" : time.time() - time_start, "state" : state_num, "T_set" : state_curr["T_set"], "P_set" : state_curr["P_set"], #"wl_ex" : state_curr["wl_ex"], #"wl_em" : state_curr["wl_em"] } ) # round off the modeled T_act data_dict["T_act"] = round(data_dict["T_act"], prec_bath) # SAFETY SECOND # check for pressure system leak if (data_dict["vol"] - vol_start) > args["vol_diff"]: pump_free.clear() pump.clear() raise Exception("Pump has discharged > {} mL!".format(args["vol_diff"])) # AIR SAVER # if transition's been running for >2x the stability time, # but bath internal temperature still not in range, # shut air off temporarily if args["air_saver"] and \ ((time_cycle - time_state) >= 2 * args["eq_min"]) and \ (data_dict["T_int"] < temp_set - temp_tol) or \ (data_dict["T_int"] > temp_set + temp_tol): if waited and not save_air: print(file=stderr) if not save_air: print("air saver activated", end='', file=stderr) save_air = True else: save_air = False # control the air system #NTS 20200720: pin set executes twice, which is annoying but not fatal if data_dict['T_act'] <= args["dewpoint"] and not save_air: # if it's cold if not data_dict['air']: # and air is off pump_free.clear() if waited: print(file=stderr) print("turning air ON", file=stderr, end=' ') pump.digital(0, 1) print("√", file=stderr) pump_free.set() data_dict['air'] = True # start a timer for air being on time_air = time.time() print("total air time {} s".format(round(time_air_tot)), file=stderr) else: # if it's warm if data_dict['air']: # and air is on pump_free.clear() if waited: print(file=stderr) print("\nturning air OFF", file=stderr, end=' ') pump.digital(0, 0) print("√", file=stderr) pump_free.set() data_dict['air'] = False # add air time to the total time_air_tot += (time.time() - time_air) print("total air time {} s".format(round(time_air_tot)), file=stderr) # does the state change require equilibration? if any([chg_prev[var] for var in args["vars_eq"]]): # if any of the slow params have changed from last state need2wait = True else: need2wait = False # put data in the temporary trailing DF trails = trails.append(data_dict, ignore_index=True) # cut the DF down to within the trailing time trails = trails[trails['watch'] >= trails['watch'].iloc[-1] - args["eq_min"]] # if the fluor reading has changed # this check takes about 0.1 ms if (trails.shape[0] == 1) or (trails["intensity"].iloc[-1] != trails["intensity"].iloc[-2]): # write data to file hand_log.write('\t'.join([str(data_dict[col]) for col in list_head])+'\n') hand_log.flush() #NTS 20200719: It would be nice to abstract pressure and temp stability! try: # note that this checks range of the internal temperature, and stability of the actual temperature temp_in_range = ((max(trails["T_int"]) <= temp_set + temp_tol) and (min(trails["T_int"]) >= temp_set - temp_tol) and ((max(trails["T_act"]) - min(trails["T_act"]) <= 2 * args["tol_T"]))) pres_in_range = ((max(trails["P_act"]) <= state_curr["P_set"] + args["tol_P"]) and (min(trails["P_act"]) >= state_curr["P_set"] - args["tol_P"])) except: # in case the dataframe is Empty temp_in_range = False pres_in_range = False pass # if we're equilibrated # and in range # or state has timed out # and windows are defogged if ((not need2wait or (time_cycle - time_state) >= args["eq_min"] and \ temp_in_range and pres_in_range and not save_air) or \ (time_cycle - time_state) >= args["eq_max"]) and \ (data_dict['T_act'] > args["dewpoint"] or (time_cycle - time_air) >= args["air_time"]) : if waited: print(file=stderr) # newline waited = False # open the shutter if (not readings) and args["auto_shut"] and any([chg_prev[var] for var in args["vars_eq"]]): spec_free.clear() while not spec.shutter(True): pass spec_free.set() # take some readings if (trails.shape[0] == 1) or (trails["intensity"].iloc[-1] != trails["intensity"].iloc[-2]): if readings: print("reading {}: {} AU\r".format(readings, trails['intensity'].iloc[-1]), end='', file=stderr) readings += 1 # break out of loop to next state if (readings > args["n_read"]): if args["auto_shut"] and any([chg_next[var] for var in args["vars_eq"]]): spec_free.clear() while not spec.shutter(False): pass spec_free.set() print(file=stderr) break else: # what are we waiting for? print("waiting {} s to get {} s of stability\r".format(round(time.time()-time_state), args["eq_min"]), end='', file=stderr) waited = True # prescribed sleep try: time.sleep((args["cycle_time"] / 1000) - (time.time() - (time_cycle))) except: pass # shut down when done pump_free.clear() pump.digital(0,0) pump.pause() pump.disconnect() bath_free.clear() bath.on(False) bath.disconnect() spec_free.clear() spec.shutter(False) spec.disconnect() sys.exit(0) except: pump_free.clear() pump.pause() pump.digital(0,0) # turn the air off! spec_free.clear() spec.ack() spec.shutter(False) traceback.print_exc()
def main(args, stdin, stdout, stderr): "Run a temperature/parameter scan and store output to a file." # if args are passed as namespace, convert it to dict try: args = vars(args) except: pass if not stdin.isatty(): # if a state table is passed on stdin, read it print("reading states from stdin", file=stderr, flush=True) states = pd.read_csv(stdin, sep='\t') else: print("ERR: you need to pass the state table on stdin!", file=stderr, flush=True) exit(1) ## run the experiment # open output file, do not overwrite! #with open(args['file_log'], 'x') as hand_log: with open(args['file_log'], 'w') as hand_log: ## variables tracking the expt schedule #vars_sched = ["clock", "watch", "state"] ## externally measured and derived variables #vars_measd = ["T_int", "T_ext", "T_act", "P", "I", "D", "P_act", "vol", "intensity", "T_amb", "H_amb", "dewpt", "air"] ## compose and write header - states.head() are the setpoint variables #list_head = vars_sched + list(states.head()) + vars_measd #line_head = "\t".join(list_head) #hand_log.write(line_head + '\n') #hand_log.flush() # now we're opening serial connections, which need to be closed cleanly on exit try: # init instruments print("connecting...", file=stderr, flush=True) print("fluorospectrometer ", end='', file=stderr, flush=True) spec = rf5301.RF5301(port=args['port_spec']) print("√", file=stderr, flush=True) print("aux microcontroller ", end='', file=stderr, flush=True) amcu = auxmcu.AuxMCU(port=args['port_amcu']) print("√", file=stderr, flush=True) print("temperature controller ", end='', file=stderr, flush=True) bath = neslabrte.NeslabController(port=args['port_bath']) print("√", file=stderr, flush=True) print("pressure controller ", end='', file=stderr, flush=True) pump = isco260D.ISCOController(port=args['port_pump']) print("√", file=stderr, flush=True) ## hardware init print("initializing...", file=stderr, flush=True) # set spec slits and gain print("spec ", end='', file=stderr, flush=True) # autozero with emission slit closed while not spec.slit_em(0): pass while not spec.zero(): pass # open the shutter, unless in auto if not args["auto_shut"]: while not spec.shutter(True): pass print('.', end='', file=stderr, flush=True) print(' √', file=stderr, flush=True) # initialize the AMCU print("auxiliary ", end='', file=stderr, flush=True) amcu.lamp(True) print(' √', file=stderr, flush=True) # start bath circulator print("bath ", end='', file=stderr, flush=True) # enter topside cal coefficients bath.cal_int.reset(*args["rtd_cal"]) # start the bath #while not bath.on(): # bath.on(True) bath.on(True) # 20220105 hack print(' √', file=stderr, flush=True) # clear and start pump print("pump", end='', file=stderr, flush=True) while not pump.remote(): pass print('.', end='', file=stderr, flush=True) while not pump.clear(): pass print('.', end='', file=stderr, flush=True) while not pump.run(): pass print('.', end='', file=stderr, flush=True) # get initial volume vol_start = None while not vol_start: vol_start = pump.vol_get() print(" √ V0 = {} mL".format(vol_start), file=stderr, flush=True) # declare async queues queue_bath = deque(maxlen=1) queue_pump = deque(maxlen=1) queue_spec = deque(maxlen=1) queue_amcu = deque(maxlen=1) # start polling threads # all device instances have RLocks! bath_free = threading.Event() pump_free = threading.Event() spec_free = threading.Event() amcu_free = threading.Event() [ event.set() for event in (bath_free, pump_free, spec_free, amcu_free) ] threading.Thread(name="pollbath", target=poll, args=(bath, bath_free, queue_bath)).start() threading.Thread(name="pollpump", target=poll, args=(pump, pump_free, queue_pump)).start() threading.Thread(name="pollspec", target=poll, args=(spec, spec_free, queue_spec)).start() threading.Thread(name="pollamcu", target=poll, args=(amcu, amcu_free, queue_amcu)).start() ## run experiment # start experiment timer (i.e. stopwatch) time_start = time.time() print("waiting for data streams...", file=stderr, flush=True) # init the data dict. Persistent at start of program. data_dict = { "clock": time.strftime("%Y%m%d %H%M%S"), "watch": time.time() - time_start, } for dq in (queue_bath, queue_pump, queue_spec, queue_amcu): while True: #print(data_dict) #TEST # ensure that *something* gets popped out so the dict is complete try: data_dict.update(dq.popleft()) break except: pass # iterate over test states for state_num in range(states.shape[0]): # make dict for this state state_curr = states.iloc[state_num + 0].to_dict() try: state_next = states.iloc[state_num + 1].to_dict() except: pass # status update print("state {}/{}:".format(state_num + 1, states.shape[0]), file=stderr, flush=True) print(state_curr, file=stderr, flush=True) # before entering the first state, write the data file header if not state_num: hand_log.write('\t'.join( sorted( set( list(state_curr.keys()) + list(data_dict.keys())))) + '\n') ## SETTING COMMANDS ## once these have executed, state_curr and data_dict can be merged ## without loss of information # set temp bath_free.clear() time.sleep(1) while not isclose( bath.temp_set(), round(bath.cal_int.act2ref(state_curr['T_set']), 2)): print("setting temperature to {}°C".format( state_curr['T_set']), file=stderr, flush=True, end=' ') bath.temp_set(bath.cal_int.act2ref(state_curr['T_set'])) print('√', file=stderr, flush=True) bath_free.set() # set pressure persistently pump_free.clear() while not isclose(pump.press_set(), state_curr['P_set']): print("setting pressure to {} bar".format( state_curr['P_set']), file=stderr, flush=True, end=' ') if pump.press_set(state_curr['P_set']): print('√', file=stderr, flush=True) pump_free.set() # mark time for start of state # doing this before wavelengths saves a few seconds time_state = time.time() waited = False # init n counter readings = 0 # set wavelengths ## set the excitation wavelength #while not isclose(spec.wl_ex(), state_curr['wl_ex']): # print("setting excitation WL to {} nm".format(state_curr['wl_ex']), file=stderr, flush=True, end=' ') # if spec.wl_ex(state_curr['wl_ex']): print('√', file=stderr, flush=True) # ## set the emission wavelength #while not isclose(spec.wl_em(), state_curr['wl_em']): # print("setting emission WL to {} nm".format(state_curr['wl_em']), file=stderr, flush=True, end=' ') # if spec.wl_ex(state_curr['wl_em']): print('√', file=stderr, flush=True) # temporary WL setters # Persistence implemented over cycles to improve efficiency; # note that this checks the previous data row. if not ( # do any requests need to be sent to the spec? state_curr['wl_ex'] == data_dict['wl_ex'] and state_curr['wl_em'] == data_dict['wl_em'] and state_curr['slit_ex'] == data_dict['slit_ex'] and state_curr['slit_em'] == data_dict['slit_em']): #spec_free.clear() # seems like maybe these flags should be removed bc they slow things down? if (state_curr['wl_ex'] == 350) and (state_curr['wl_em'] == 428): print("setting wavelengths for DPH", file=stderr, flush=True, end=' ') if spec.wl_set_dph(): print('√', file=stderr, flush=True) elif (state_curr['wl_ex'] == 340) and (state_curr['wl_em'] == 440): print("setting wavelengths to 340/440", file=stderr, flush=True, end=' ') if spec.wl_set_340_440(): print('√', file=stderr, flush=True) elif (state_curr['wl_ex'] == 340) and (state_curr['wl_em'] == 490): print("setting wavelengths to 340/490", file=stderr, flush=True, end=' ') if spec.wl_set_340_490(): print('√', file=stderr, flush=True) elif (state_curr['wl_ex'] == 410) and (state_curr['wl_em'] == 440): print("setting wavelengths to 410/440", file=stderr, flush=True, end=' ') if spec.wl_set_410_440(): print('√', file=stderr, flush=True) elif (state_curr['wl_ex'] == 410) and (state_curr['wl_em'] == 490): print("setting wavelengths to 410/490", file=stderr, flush=True, end=' ') if spec.wl_set_410_490(): print('√', file=stderr, flush=True) # slits if state_curr['slit_ex'] != data_dict['slit_ex']: print("setting ex slit to {} nm".format( state_curr['slit_ex']), file=stderr, flush=True, end=' ') if spec.slit_ex(state_curr['slit_ex']): print('√', file=stderr, flush=True) if state_curr['slit_em'] != data_dict['slit_em']: print("setting em slit to {} nm".format( state_curr['slit_em']), file=stderr, flush=True, end=' ') if spec.slit_em(state_curr['slit_em']): print('√', file=stderr, flush=True) #spec_free.set() # add input data to output buffer data_dict.update(state_curr) # init buffer for previous data line data_prev = {key: None for key in data_dict.keys()} # data logging loop while True: time_cycle = time.time() # DATA FIRST for dq in (queue_bath, queue_pump, queue_spec, queue_amcu): try: data_dict.update(dq.popleft()) break except: pass # add timing data data_dict.update({ "clock": time.strftime("%Y%m%d %H%M%S"), "watch": time.time() - time_start, }) ## Cyclewise instrument control ## for stuff that needs to be monitored in real time # SAFETY SECOND # check for pressure system leak if (data_dict["vol"] - vol_start) > args["vol_diff"]: pump_free.clear() pump.clear() raise Exception("Pump has discharged > {} mL!".format( args["vol_diff"])) # control the air system # if it's cold and air is off if (data_dict['T_act'] <= data_dict["dewpt"] + args["dew_tol"]) and not data_dict['air']: pump_free.clear() if waited: print(file=stderr, flush=True) print("turning air ON", file=stderr, flush=True, end=' ') while not pump.digital(0, 1): pass print("√", file=stderr, flush=True, end='\r') pump_free.set() data_dict['air'] = True # if it's warm and the air is on elif (data_dict['T_act'] > (data_dict["dewpt"] + args["dew_tol"])) and data_dict['air']: # and air is on pump_free.clear() if waited: print(file=stderr, flush=True) print("turning air OFF", file=stderr, flush=True, end=' ') while not pump.digital(0, 0): pass print("√", file=stderr, flush=True, end='\r') pump_free.set() data_dict['air'] = False #print("P "+str(data_prev["intensity"])) #print("C "+str(data_dict["intensity"])) # if state wait time has not elapsed waiting = (time.time() - time_state) < data_dict["time"] if waiting: print("waiting {}/{} s ".format( round(time.time() - time_state), data_dict["time"]), file=sys.stderr, end='\r', flush=True) waited = True else: if waited: print(file=stderr, flush=True) # newline # open the shutter if (not readings) and args["auto_shut"] and waited: spec_free.clear() # good or bad? while not spec.shutter(True): pass spec_free.set() time_open = time.time() # reset waited = False # fluor intensity is the fastest-changing variable # so only save data when it changes # this is the output block if data_dict["intensity"] != data_prev["intensity"]: # write data to file whether it counts as a reading or not hand_log.write('\t'.join([ str(data_dict[key]) for key in sorted(data_dict.keys()) ]) + '\n') hand_log.flush() # and buffer the data data_prev.update(data_dict) if not waiting: # increment the reading count readings += 1 print("reading {}/{} s: {} AU ".format( readings, data_dict["n_read"], data_dict["intensity"]), file=sys.stderr, end='\r', flush=True) # once sufficient readings have been taken if readings >= data_dict["n_read"]: print(file=stderr, flush=True) # newline # if there is a wait between states, close shutter if state_next["time"]: spec_free.clear() # good or bad? while not spec.shutter(False): pass spec_free.set() time_shut = time.time() # escape to next state break # shut down when done pump_free.clear() pump.digital(0, 0) pump.pause() pump.disconnect() bath_free.clear() bath.on(False) bath.disconnect() spec_free.clear() spec.shutter(True) spec.disconnect() amcu_free.clear() amcu.__ser__.send_break(duration=amcu.__ser__.timeout + 0.1) time.sleep(amcu.__ser__.timeout + 0.1) amcu.lamp(False) sys.exit(0) except: # on failure: pump_free.clear() pump.pause() pump.digital(0, 0) # turn the air off! spec_free.clear() spec.ack() spec.shutter(False) amcu_free.clear() amcu.__ser__.send_break(duration=amcu.__ser__.timeout + 0.1) time.sleep(amcu.__ser__.timeout + 0.1) amcu.lamp(False) traceback.print_exc()
def main(args, stdin, stdout, stderr): "Run a temperature/parameter scan and store output to a file." # if args are passed as namespace, convert it to dict try: args = vars(args) except: pass if not stdin.isatty(): # if a state table is passed on stdin, read it print("reading states from stdin", file=stderr) states = pd.read_csv(stdin, sep='\t') else: print("ERR: you need to pass the state table on stdin!", file=stderr) exit(1) ## run the experiment # open output file, do not overwrite! with open(args['file_log'], 'x') as hand_log: # variables tracking the expt schedule vars_sched = ["clock", "watch", "state"] # externally measured and derived variables vars_measd = [ "T_int", "T_ext", "T_act", "P", "I", "D", "P_act", "vol", "intensity", "T_amb", "H_amb", "dewpt", "air" ] # compose and write header - states.head() are the setpoint variables list_head = vars_sched + list(states.head()) + vars_measd line_head = "\t".join(list_head) hand_log.write(line_head + '\n') hand_log.flush() # now we're opening serial connections, which need to be closed cleanly on exit try: # init instruments print("connecting...", file=stderr) print("fluorospectrometer √", file=stderr) amcu = auxmcu.AuxMCU(port=args['port_amcu']) print("aux microcontroller √", file=stderr) bath = isotemp6200.IsotempController(port=args['port_bath']) print("temperature controller √", file=stderr) pump = isco260D.ISCOController(port=args['port_pump']) print("pressure controller √", file=stderr) spec = rf5301.RF5301(port=args['port_spec']) ## hardware init print("initializing...", file=stderr) # set spec slits and gain print("spec", end='', file=stderr) # open the shutter, unless in auto if not args["auto_shut"]: while not spec.shutter(True): pass print('.', end='', file=stderr) print(' √', file=stderr) # initialize the filter wheels print("auxiliary", file=stderr) amcu.lamp(True) #amcu.wheels_init() # start bath circulator print("bath", end='', file=stderr) # init topside PID pid = PID(1, 0, 85, setpoint=states.loc[states.index[0], "T_set"]) # windup preventer pid.output_limits = (-20, 20) # enter topside cal coefficients bath.cal_ext.reset(*args["rtd_cal"]) # set controller gains while not all(bath.pid('H', 0.8, 0, 0)): pass print('.', end='', file=stderr) while not all(bath.pid('C', 1, 0, 0)): pass print('.', end='', file=stderr) # set precision (number of decimal places) while not bath.temp_prec(2): pass print('.', end='', file=stderr) # set controller to listen to external RTD while not bath.probe_ext(True): pass print('.', end='', file=stderr) # finally, start the bath while not bath.on(): bath.on(True) print(' √', file=stderr) # clear and start pump print("pump", end='', file=stderr) while not pump.remote(): pass print('.', end='', file=stderr) while not pump.clear(): pass print('.', end='', file=stderr) while not pump.run(): pass print('.', end='', file=stderr) # get initial volume vol_start = None while not vol_start: vol_start = pump.vol_get() print(" √ V0 = {} mL".format(vol_start), file=stderr) # declare async queues queue_bath = deque(maxlen=1) queue_pump = deque(maxlen=1) queue_spec = deque(maxlen=1) queue_amcu = deque(maxlen=1) # start polling threads # all device instances have RLocks! bath_free = threading.Event() pump_free = threading.Event() spec_free = threading.Event() amcu_free = threading.Event() [ event.set() for event in (bath_free, pump_free, spec_free, amcu_free) ] threading.Thread(name="pollbath", target=poll, args=(bath, bath_free, queue_bath, pid)).start() threading.Thread(name="pollpump", target=poll, args=(pump, pump_free, queue_pump)).start() threading.Thread(name="pollspec", target=poll, args=(spec, spec_free, queue_spec)).start() threading.Thread(name="pollamcu", target=poll, args=(amcu, amcu_free, queue_amcu)).start() ## run experiment # dict links setp vars to meas vars #NTS 20210113 there's gotta be a better place for these mappings setp2meas = {"T_set": "T_act", "P_set": "P_act"} # start experiment timer (i.e. stopwatch) time_start = time.time() # iterate over test states for state_num in range(states.shape[0]): # make dicts for this state, the last, and the next # takes about 1 ms state_curr = states.iloc[state_num + 0].to_dict() if state_num: state_prev = states.iloc[state_num - 1].to_dict() else: # if first state state_prev = {key: 0 for key in state_curr.keys()} chg_prev = {key: True for key in state_curr.keys()} if state_num < states.shape[0] - 1: state_next = states.iloc[state_num + 1].to_dict() else: # if final state state_next = {key: 0 for key in state_curr.keys()} chg_next = {key: True for key in state_curr.keys()} # which params have changed since previous state? chg_prev = { key: (state_curr[key] != state_prev[key]) for key in state_curr.keys() } chg_next = { key: (state_curr[key] != state_next[key]) for key in state_curr.keys() } time_state = time.time() # mark time when state starts waited = False # did the state have to wait for stability? sat = False # did the dye have to relax? readings = 0 # reset n counter # status update print("state {}/{}:".format(state_num + 1, states.shape[0]), file=stderr) print(state_curr, file=stderr) # data logging loop data_dict = {} # init the data dict. Persistent the first time. for dq in (queue_bath, queue_pump, queue_spec, queue_amcu): while True: # ensure that *something* gets popped out so the dict is complete try: data_dict.update(dq.popleft()) break except: pass # set temp via topside PID while not isclose(pid.setpoint, state_curr['T_set']): print("setting temperature to {}˚C".format( state_curr['T_set']), file=stderr, end=' ') # pid object is picked up by poll() pid.setpoint = state_curr['T_set'] print('√', file=stderr) # set pressure persistently pump_free.clear() while not isclose(pump.press_set(), state_curr['P_set']): print("setting pressure to {} bar".format( state_curr['P_set']), file=stderr, end=' ') if pump.press_set(state_curr['P_set']): print('√', file=stderr) pump_free.set() ## set the excitation wavelength #while not isclose(spec.ex_wl(), state_curr['wl_ex']): # print("setting excitation WL to {} nm".format(state_curr['wl_ex']), file=stderr, end=' ') # if spec.ex_wl(state_curr['wl_ex']): print('√', file=stderr) # ## set the emission wavelength #while not isclose(spec.em_wl(), state_curr['wl_em']): # print("setting emission WL to {} nm".format(state_curr['wl_em']), file=stderr, end=' ') # if spec.ex_wl(state_curr['wl_em']): print('√', file=stderr) # temporary WL setters # Persistence implemented over cycles to improve efficiency; # note that this checks the previous data row. if not ((state_curr['wl_ex'] == data_dict['wl_ex']) and (state_curr['wl_em'] == data_dict['wl_em'])): #if not (isclose(spec.ex_wl(), state_curr['wl_ex']) and isclose(spec.em_wl(), state_curr['wl_em'])): if (state_curr['wl_ex'] == 350) and (state_curr['wl_em'] == 428): print("setting wavelengths for DPH", file=stderr, end=' ') spec_free.clear() spec.wl_set_dph() spec_free.set() print('√', file=stderr) # set polarizers if not ((state_curr['pol_ex'] == data_dict['pol_ex']) and (state_curr['pol_em'] == data_dict['pol_em'])): amcu_free.clear() # take the line from other thread! amcu.__ser__.send_break(duration=amcu.__ser__.timeout + 0.1) time.sleep(amcu.__ser__.timeout + 0.1) # excitation if state_curr['pol_ex'] != data_dict['pol_ex']: print("setting ex polarization to {}".format( state_curr['pol_ex']), file=stderr, end=' ') while not state_curr['pol_ex'] == amcu.ex( state_curr['pol_ex']): pass print('√', file=stderr) # emission if state_curr['pol_em'] != data_dict['pol_em']: print("setting em polarization to {}".format( state_curr['pol_em']), file=stderr, end=' ') while not state_curr['pol_em'] == amcu.em( state_curr['pol_em']): pass print('√', file=stderr) amcu_free.set() # wait until wheel is solidly in position before writing any data time.sleep(amcu.__ser__.timeout) # init a log table for the state trails = pd.DataFrame(columns=list_head) while True: time_cycle = time.time() # DATA FIRST for dq in (queue_bath, queue_pump, queue_spec, queue_amcu): try: data_dict.update(dq.popleft()) break except: pass # add internal data data_dict.update({ "clock": time.strftime("%Y%m%d %H%M%S"), "watch": time.time() - time_start, "state": state_num, "T_set": state_curr["T_set"], "P_set": state_curr["P_set"], "msg": state_curr["msg"] }) # SAFETY SECOND # check for pressure system leak if (data_dict["vol"] - vol_start) > args["vol_diff"]: pump_free.clear() pump.clear() raise Exception("Pump has discharged > {} mL!".format( args["vol_diff"])) # control the air system # if it's cold and air is off if (data_dict['T_act'] <= data_dict["dewpt"] + args["dew_tol"]) and not data_dict['air']: pump_free.clear() if waited: print(file=stderr) print("\nturning air ON", file=stderr, end=' ') while not pump.digital(0, 1): pass print("√", file=stderr) pump_free.set() data_dict['air'] = True # if it's warm and the air is on elif (data_dict['T_act'] > (dewpt(data_dict['H_amb'], data_dict['T_amb']) + args["dew_tol"])) and data_dict['air']: # and air is on pump_free.clear() if waited: print(file=stderr) print("\nturning air OFF", file=stderr, end=' ') while not pump.digital(0, 0): pass print("√", file=stderr) pump_free.set() data_dict['air'] = False # does the state change require equilibration? vars_wait = [var for var in args["eqls"] if chg_prev[var]] # if this in an empty dict, all(in_range.values()) will be true in_range = {var: False for var in vars_wait} # put new data into a trailing buffer trails = trails.append(data_dict, ignore_index=True) # cut the buffer down to the min equil time of the slowest variable if vars_wait: trails = trails[trails['watch'] >= ( trails['watch'].iloc[-1] - max([min(args["eqls"][var]) for var in vars_wait]))] # if the fluor reading has changed, write line to logfile # this check takes about 0.1 ms if (trails.shape[0] == 1) or (trails["intensity"].iloc[-1] != trails["intensity"].iloc[-2]): # write data to file hand_log.write('\t'.join( [str(data_dict[col]) for col in list_head]) + '\n') hand_log.flush() for var in vars_wait: # if variable's timeout is past if (time_cycle - time_state) >= max(args["eqls"][var]): in_range[var] = True # else, if min equilibration has elapsed elif (time_cycle - time_state) >= min( args["eqls"][var]): #print("{}: {}\r".format(var, data_dict[var]), end='') # see if the trace of the variable is in range trace = trails[trails['watch'] >= ( trails['watch'].iloc[-1] - min(args["eqls"][var]))][ setp2meas[var]].tolist() #print(trace) # and green- or redlight the variable as appropriate in_range[var] = ((max(trace) < (state_curr[var] + args["tols"][setp2meas[var]])) and (min(trace) > (state_curr[var] - args["tols"][setp2meas[var]]))) # if all equilibrations have cleared if all(in_range.values()): if waited: print(file=stderr) # newline waited = False # open the shutter if (not readings) and args["auto_shut"] and len( vars_wait) and not sat: spec_free.clear() while not spec.shutter(True): pass spec_free.set() time_open = time.time() # let the dye relax after a long dark period (temp xsition) if ("T_set" not in vars_wait) or ( (time.time() - args["shut_sit"]) >= time_open): # take some readings if (trails.shape[0] == 1) or (trails["intensity"].iloc[-1] != trails["intensity"].iloc[-2]): if readings: print("reading {}: {} AU \r".format( readings, trails['intensity'].iloc[-1]), end='', file=stderr) readings += 1 # break out of loop to next state if (readings > args["n_read"]): if args["auto_shut"] and any([ chg_next[var] for var in args["eqls"].keys() ]): spec_free.clear() while not spec.shutter(False): pass spec_free.set() print(file=stderr) break else: print("dye relaxed for {}/{} s\r".format( round(time.time() - time_open), args["shut_sit"]), end='', file=stderr) sat = True # gotta get a newline in here somewhere! else: # what are we waiting for? print("waiting {} s to get {} s of stability\r".format( round(time.time() - time_state), max([min(args["eqls"][var]) for var in vars_wait])), end='', file=stderr) waited = True # prescribed sleep try: time.sleep((args["cycle_time"] / 1000) - (time.time() - (time_cycle))) except: pass # shut down when done pump_free.clear() pump.digital(0, 0) pump.pause() pump.disconnect() bath_free.clear() bath.on(False) bath.disconnect() spec_free.clear() spec.shutter(True) spec.disconnect() amcu_free.clear() amcu.__ser__.send_break(duration=amcu.__ser__.timeout + 0.1) time.sleep(amcu.__ser__.timeout + 0.1) amcu.lamp(False) amcu.ex('0') amcu.em('0') sys.exit(0) except: pump_free.clear() pump.pause() pump.digital(0, 0) # turn the air off! spec_free.clear() spec.ack() spec.shutter(False) amcu_free.clear() amcu.__ser__.send_break(duration=amcu.__ser__.timeout + 0.1) time.sleep(amcu.__ser__.timeout + 0.1) amcu.lamp(False) amcu.ex('0') amcu.em('0') traceback.print_exc()
#!/usr/bin/python3 """ initialize Isotemp water bath, then provide user with an interactive console for debugging """ import rf5301 import traceback spec = rf5301.RF5301(port="/dev/cu.usbserial-FTV5C58R0") while True: cmd = input("spec.") try: ret = eval("spec.{}".format(cmd)) print(ret) except: spec.__ser__.flush() traceback.print_exc()