def poll_serial(): """ Find ports for the bath, pump, spec, and swapper. Connect and return their objects in a dict. """ # returns ListPortInfo object, which is a tuple of tuples # where [0] of inner tuple is port name ports = serial.tools.list_ports.comports(include_links=True) devs = dict() num_devs = 4 for port in ports: # try to connect bath try: devs["bath"] = isotemp6200.IsotempController(port[0], baud=9600, timeout=1) except: pass # try to connect pump try: devs["pump"] = isco260D.ISCOController(port[0], baud=9600, timeout=1, source=0, dest=1) except: pass # try to connect spec try: devs["spec"] = rf5301PC.IsotempController(port[0], baud=9600, timeout=1) except: pass # try to connect filter swapper try: devs["swap"] = filterswapper.FilterController(port[0], baud=9600, timeout=1) except: pass # report all the devices connected print(devs) # Throw an error if any device is missing if len(devs.keys() < num_devs): raise DeviceNotFoundError() return devs
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) 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()
self.__ser__.flush() return float(self.__ser__.readline().decode().strip()) # open output file with open(file_log, 'w') as hand_log: # write header hand_log.write("\t".join(["clock", "watch", "T_int", "T_ext", "T_ref"]) + '\n') hand_log.flush() # now we're opening serial connections, which need to be closed cleanly on exit try: # init water bath bath = isotemp.IsotempController(port="/dev/cu.usbserial-AL01M1X9") #nist = QTIProbe(port="/dev/cu.usbmodem606318311") nist = QTIProbe(port="/dev/cu.usbmodem1421301") ## run experiment # start experiment timer (i.e. stopwatch) time_start = time.time() # start circulator while not bath.on(): bath.on(True) # set precision while not bath.temp_prec(2): pass
temps = np.arange(*[ float(x) for x in (config["temps"]["min"], config["temps"]["max"], config["temps"]["step"]) ]).tolist() press = np.arange(*[ float(x) for x in (config["press"]["min"], config["press"]["max"], config["press"]["step"]) ]).tolist() # open output data file with open(config["files"]["data"], 'x') as file_data: # now we're opening serial connections, which need to be closed cleanly on exit try: # init water bath bath = isotemp.IsotempController(port=config["ports"]["bath"]) # columns in the data table cols = ("clock", "watch", "temp_set", "temp_act") # write header file_data.write('\t'.join(cols) + '\n') # start experiment timer (i.e. stopwatch) time_start = time.time() ## run experiment # start circulator while not bath.on(): print("starting bath") bath.on(True) for temp in temps: # set temp persistently
def main(args, stdin, stdout, stderr, aux=None): "Run a temperature/parameter scan and store output to a file. Aux is query method for an auxiliary sensor." # 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: # generate state table from args ranges = { "T_set" : np.arange(*args['range_T_set']).tolist(), "Cp" : np.arange(*args['range_Cp']).tolist(), "Ci" : np.arange(*args['range_Ci']).tolist(), "Cd" : np.arange(*args['range_Cd']).tolist(), "Hp" : np.arange(*args['range_Hp']).tolist(), "Hi" : np.arange(*args['range_Hi']).tolist(), "Hd" : np.arange(*args['range_Hd']).tolist() } # calculate cartesian product of these ranges states = pd.DataFrame(list(product_dict(**ranges))) # sort for efficient transitions # parameters on the right change faster #NTS 20200714 .iloc[::-1] reversal is #temporary, idk why ascending= doesn't work! states = states.sort_values(by=args['scan_rank'], ascending=args['scan_asc']).reset_index(drop=True).iloc[::-1] # print the generated table to stdout for records states.to_csv(stdout, sep='\t') ## 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", "T_int", "T_ext"] + list(ranges.keys()) # insert T_aux column if aux provided if aux: list_head.insert(4, "T_aux") 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 water bath bath = isotemp.IsotempController(args['port_isotemp']) ## run experiment # start experiment timer (i.e. stopwatch) time_start = time.time() # start circulator while not bath.on(): bath.on(True) # set precision while not bath.temp_prec(2): pass # iterate over test states for state in states.itertuples(): oscs = 0 state_start = time.time() print("state {}/{}:".format(state[0], states.shape[0]), file=stderr) print(state[1:], file=stderr) # set temp persistently while not isclose(bath.temp_set(), state._asdict()['T_set']): print("setpoint to {}".format(state._asdict()['T_set']), file=stderr) bath.temp_set(state._asdict()['T_set']) for drive in ('C', 'H'): print("{} to PID {}".format(drive, [state._asdict()[drive + 'p'], state._asdict()[drive + 'i'], state._asdict()[drive + 'd']]), file=stderr) while not all(bath.pid(drive, state._asdict()[drive + 'p'], state._asdict()[drive + 'i'], state._asdict()[drive + 'd'])): pass # data logging loop trace = [bath.temp_get_ext()] * 3 peaks = [] valleys = [] while True: temp_int = bath.temp_get_int() temp_ext = bath.temp_get_ext() list_data = [ # date, clock time time.strftime("%Y%m%d %H%M%S"), # watch time str(round(time.time() - time_start, 3)), # temps temp_int, temp_ext ] + list(state[1:]) # insert T_aux column if aux provided if aux: list_head.insert(4, aux()) line_data = '\t'.join([str(x) for x in list_data]) hand_log.write(line_data+'\n') hand_log.flush() # if temp has changed by 0.1˚C, update trend if abs(temp_ext - trace[-1]) > 0.1: trace = trace[1:] + [temp_ext] # oscillation counter if trace[0] < trace[1] > trace[2]: peaks.append(trace[1]) # update trace trace[2] = trace[1] elif trace[0] > trace[1] < trace[2]: valleys.append(trace[1]) # update trace trace[2] = trace[1] oscs = min(len(peaks), len(valleys)) # oscillation count bailout if oscs >= args['oscillations']: print("{} oscillations completed".format(oscs), file=stderr) break # time bailout elif (time.time() - state_start) > args['timeout']: print("args['timeout'] {} s reached after {} oscillations".format(args['timeout'], oscs), file=stderr) break # shut down when done bath.on(False) bath.disconnect() except: bath.disconnect() traceback.print_exc()