Automatic commit. Fri Sep 7 00:00:05 WST 2012
[matches/honours.git] / research / TCS / interface.py
1 #!/usr/bin/python
2
3 # @file "interface.py"
4 # @author Sam Moore
5 # @purpose AVR Butterfly Datalogger control for Total Current Spectroscopy Measurements
6 #       The AVR responds to a limited number of RS232 commands to read ADC channels and set the DAC
7 #       This script can be used to control the DAC and monitor initial energy & sample current over RS232
8
9 import sys
10 import os
11 import time
12 import serial
13 import datetime
14
15 import odict
16 import Gnuplot, Gnuplot.funcutils
17
18 gnuplot = Gnuplot.Gnuplot()
19
20 # TODO: Insert variables for calibration purposes here.
21 calibrate = {
22         "ADC_Counts" : [2**10,2**10,2**10,2**10,2**10,2**10,2**10], # Maxed counts on ADC channels (10 bits = 2**10 = 1024)
23         "DAC_Counts" : 2**12, # Maxed counts on DAC channel (12 bits = 2**12 = 4096)
24
25         # Calibration data for DAC and ADC
26         "DAC" : None, 
27         "ADC" : [None, None, None, None, None, None, None, None],
28
29         # Data used for rough calibration if above data is not present
30         "Vref" : 3.3, # The output of the voltage regulator (Vref = Vcc) relative to signal ground
31         "ADC_Rin" : [None, None, None, None, 15000, 1100, 1100, 1100],
32         "ADC_Rvar" : [None, None, None, None, 1000, 5000, 10000, 50000],
33         "DAC_Gain" : 1
34 }
35
36 # TODO: Adjust aqcuisition parameters here
37 aquire = { "DAC_Sweep" : "0.0 + 50.0*int(step)", # DAC Sweep value (t is in STEPS, not seconds!)
38         "ADC_Averages" : 200,
39         #"ADC_Vi" : 5, # ADC channel to read back Vi (set by DAC) through
40         #"ADC_Is" : 4, # ADC channel to read back Is through
41         #"ADC_Ie" : 4, # ADC channel to read back Ie through
42         "DAC_Settle" : 0.0, # Time in seconds to wait for DAC to stabilise
43         #"response_wait" : 0.2, # Time to wait in seconds between sending data and reading back
44         "start_date" : None,
45         "open_files" : []
46 }
47
48 #Setup the serial connection parameters
49 ser = serial.Serial(
50         port="/dev/ttyUSB1", # Modify as needed (note: in linux need to run `sudo chmod a+rw /dev/ttyUSBX' to set permissions)
51
52         # Do not change the values below here (unless AVR butterfly is reprogrammed to use different values)
53         baudrate=4800,
54         parity="N",
55         stopbits=1,
56         bytesize=8,
57         timeout=None,
58         xonxoff=0,
59         rtscts=0
60 )
61
62 parameters = odict.odict([
63         ("Accelerating Voltage" , None),
64         ("Focus Voltage" , None),
65         ("Deflection Voltage" , None),
66         ("Venault Voltage" , None),
67         ("Initial Voltage" , None),
68         ("Heating Current" , None),
69         ("Heating Voltage (across filament)" , None),
70         ("Heating Voltage (across power supply)", None),
71         ("Chamber Pressure" , None),
72         #("610B Zero" , None),
73         ("602 Zero" , None),
74         #("610B Scale" , None),
75         ("602 Scale" , None),
76         ("602 0.1 Battery" , None),
77         ("602 0.03 Battery" , None),
78         ("602 0.01 Battery" , None),
79         ("602 0.003 Battery" , None),
80         ("602 0.001 Battery" , None), 
81         ("ADC Regulator" , None),
82         ("Sample", None),
83         ("Sample Angle", None),
84         ("Title" , None),
85         ("Comment" , None),
86         ("Data" , None),
87         ("Parameters last checked", None)
88 ])
89
90 def getTime():
91         return str(datetime.datetime.now()).split(" ")[1].split(".")[0].replace(":","")
92
93 def getDate():
94         return str(datetime.datetime.now()).split(" ")[0]
95
96 # Used for when I press Control-C to stop things
97 def set_exit_handler(func):
98     if os.name == "nt":
99         try:
100             import win32api
101             win32api.SetConsoleCtrlHandler(func, True)
102         except ImportError:
103             version = ".".join(map(str, sys.version_info[:2]))
104             raise Exception("pywin32 not installed for Python " + version)
105     else:
106         import signal
107         signal.signal(signal.SIGTERM, func)
108         signal.signal(signal.SIGINT, func)
109
110 def killed_handler(signal, frame):
111         reason = ""
112         sys.stdout.write("\n# Reason for killing program? ")
113         reason = sys.stdin.readline().strip("\r\n ")
114         for out in aquire["open_files"]:
115                 sys.stdout.write("# Closing file " + str(out) + "\n")
116                 out.write("# Recieved KILL signal.\n# Reason: " + str(reason) + "\n")
117                 log_close(out)
118
119         
120
121 def cleanup():
122         for out in aquire["open_files"]:
123                 out.write("# Program exits.\n")
124                 log_close(out)
125
126 def log_open(a, b):
127         result = open(a, b,0)
128         if (b == "w"):
129                 result.write("# File opened at " + str(datetime.datetime.now()) + "\n")
130                 aquire["open_files"].append(result)
131         return result
132
133 def log_close(afile):
134         if (afile in aquire["open_files"]):
135                 afile.write("# File closed at " + str(datetime.datetime.now()) + "\n")
136                 aquire["open_files"].remove(afile)
137         
138         afile.close()
139
140
141 def init():
142         #import atexit
143         #atexit.register(cleanup)
144         set_exit_handler(killed_handler)
145         
146         aquire["start_date"] = getDate()
147
148
149
150         # Connect serial
151         ser.open()
152         ser.isOpen()
153 #       print("Waiting for \"# hello\" from device...")
154 #       while (ser.readline().strip("\r\n") != "# hello"):
155 #               pass
156         #while (ser.readline().strip("\r\n ") != "#"):
157         #       pass
158         time.sleep(1.0)
159
160         ser.write("a "+str(aquire["ADC_Averages"]) + "\r\n")
161         print(ser.readline().strip("\r\n"))
162         print(ser.readline().strip("\r\n"))
163         print(ser.readline().strip("\r\n"))
164
165         #print("Writing config information to config.dat...")
166         #output = log_open("config.dat", "w", 1)
167
168         #output.write("# Initialise " + str(datetime.datetime.now()) + "\n")
169
170         #for field in calibrate:
171         #       output.write("# calibrate["+str(field)+"] = "+str(calibrate[field]) + "\n")
172         #output.write("\n")
173         #for field in aquire:
174         #       output.write("# aquire["+str(field)+"] = "+str(aquire[field]) + "\n")
175
176         #output.write("# Ready " + str(datetime.datetime.now()) + "\n# EOF\n")
177         #output.close()
178
179 def main():
180
181         init()
182         
183         # I haven't ever used calibrated results, and yet this code is still here, why???
184         #if (loadCalibration_DAC() == False):
185         #       if (calibrateDAC() == False):
186         #               return -1
187         #if (loadCalibration_ADC(aquire["ADC_Is"]) == False):
188         #       if (calibrateADC_usingDAC(aquire["ADC_Is"], False) == False):
189         #               if (calibrateADC(aquire["ADC_Is"]) == False):
190         #                       return -1
191
192         #if (loadCalibration_ADC(aquire["ADC_Vi"]) == False):
193         #       if (calibrateADC_usingDAC(aquire["ADC_Vi"], True) == False):
194         #               if (calibrateADC(aquire["ADC_Vi"]) == False):
195         #                       return -1
196         
197
198         # Make directory for today, backup calibration files
199         os.system("mkdir -p " + getDate())
200         #os.system("cp *.dat " + getDate() +"/")
201
202         checkList()
203
204         
205         
206                 
207
208         # Experiment
209         # TODO: Modify data to record here
210         sweep = 1
211         for i in range(0,1):
212                 os.system("mkdir -p " + getDate())
213                 record_data([5], getDate()+"/"+str(getTime())+".dat", None, 4000)
214                 sweep += 1
215         setDAC(500)
216
217 def checkList():
218         try:
219                 input_file = log_open(getDate()+"/checklist", "r")
220         except:
221                 input_file = None
222
223         if (input_file != None):
224                 for line in input_file:
225                         k = line.split("=")
226                         item = None
227                         if (len(k) >= 2):
228                                 item = k[0].strip("# \r\n")
229                                 value = k[1].strip("# \r\n")
230
231                         if (item in parameters):
232                                 parameters[item] = value
233         
234                 print("Checklist found. Overwrite? [Y/n]")
235                 response = sys.stdin.readline().strip(" \r\n")
236                 if (response == "" or response == "y" or response == "Y"):
237                         input_file = log_open(getDate()+"/checklist.old", "w")
238                         for item in parameters:
239                                 input_file.write("# " + str(item) + " = " + str(parameters[item]) + "\n")
240                         input_file.write("\n")
241                         log_close(input_file)
242                         input_file = None
243         
244         if (input_file == None):
245                 for item in parameters:
246                         if item == "Parameters last checked":
247                                 continue
248                         sys.stdout.write("\""+str(item)+"\" = " + str(parameters[item]) + " New value?: ")
249                         response = sys.stdin.readline().strip("\r\n ")
250                         if (response != ""):
251                                 parameters[item] = response
252                         sys.stdout.write("\n")
253                 parameters["Parameters last checked"] = str(datetime.datetime.now())
254                         
255
256         checklist = log_open(getDate()+"/checklist", "w")
257         for item in parameters:
258                 checklist.write("# "+str(item) + " = " + str(parameters[item]) + "\n")
259                 #output_file.write("# "+str(item) + " = " + str(parameters[item]) + "\n")
260         log_close(checklist)
261         
262
263 def record_data(ADC_channels, output, pollTime = None, dac_max = None):
264         
265         if (output != None):
266                 gnuplot("set title \""+str(output)+"\"")
267                 output = [log_open(output, "w"), sys.stdout]
268
269         else:
270                 gnuplot("set title \"<No file>\"")
271                 output = [sys.stdout]
272
273         for field in aquire:
274                 for out in output:
275                         out.write("# aquire["+str(field)+"] = "+str(aquire[field]) + "\n")
276         
277         for out in output:
278                 out.write("# Parameters:\n")
279
280         for field in parameters:
281                 for out in output:
282                         out.write("# "+str(field)+" = " + str(parameters[field]) + "\n")
283
284
285         start_time = time.time()
286         
287         gnuplot("set xlabel \"DAC (counts)\"")
288         gnuplot("set ylabel \"ADC (counts)\"")
289         
290         
291         for out in output:
292                 out.write("\n")
293                 out.write("# Experiment " + str(datetime.datetime.now()) + "\n")
294                 out.write("# Polling for " + str(pollTime) + "s.\n")
295                 out.write("\n")
296                 out.write("# Data:\n")
297                 out.write("# time\tDAC")
298                 for channel in ADC_channels:
299                         out.write("\tADC"+str(channel))
300                 out.write("\n")
301
302         step = 0
303         data = [] # Keep track of data for dynamic plotting
304         dacValue = int(eval(aquire["DAC_Sweep"]))
305         if (setDAC(dacValue) == False):
306                 setDAC(dacValue)
307         time.sleep(2.0)
308         while (pollTime == None or time.time() < start_time + pollTime):
309                 if (aquire["DAC_Sweep"] != None):
310                         nextDacValue = int(eval(aquire["DAC_Sweep"]))
311                         if (nextDacValue != dacValue):
312                                 dacValue = nextDacValue
313                                 if (dacValue < 0):
314                                         break
315                                 setDAC(dacValue)
316                         step += 1
317                 
318
319                 if (dac_max != None and dacValue >= dac_max):
320                         break
321
322                 measure_start = time.time()
323
324                 raw_adc = []
325
326                 for channel in ADC_channels:
327                         read = readADC(channel)
328                         if read == False:
329                                 for out in output:              
330                                         print("# Abort data collection due to failed ADC read")                                 
331                                         if out != sys.stdout:
332                                                 log_close(out)
333                                         return False
334                         raw_adc.append((channel, read[0], read[1]))
335
336                 end_time = time.time()
337
338                 for out in output:
339                         measure_time = measure_start + (end_time - measure_start)/2.0 - start_time
340                         out.write(str(measure_time))
341                         out.write("\t"+str(dacValue))
342                         data.append([measure_time, dacValue])
343
344                         for adc in raw_adc:
345                                 out.write("\t" + str(adc[1]) + "\t" + str(adc[2]))
346                                 data[len(data)-1].append(adc[1])
347                                 data[len(data)-1].append(adc[2])
348                         out.write("\n") 
349         
350         
351                 #gnuplot.plot(Gnuplot.Data(data, title="t = "+str(measure_time), with_="lp", using="2:3"))
352         for out in output:              
353                 if out != sys.stdout:
354                         log_close(out)
355         return True
356
357 def loadCalibration_ADC(channel):
358         try:
359                 input_file = log_open("calibrateADC"+str(channel)+".dat")
360         except:
361                 print("Couldn't find calibration file for ADC " + str(channel))
362                 return False
363         
364         calibrate["ADC"][channel] = []
365         for l in input_file:
366                 l = l.split("#")[0].strip("\r\n ")
367                 if (l == ""):
368                         continue
369                 else:
370                         split_line = l.split("\t")
371                         calibrate["ADC"][channel].append((float(split_line[0]), float(split_line[1])))
372         log_close(input_file)
373
374         if (len(calibrate["ADC"][channel]) <= 0):
375                 print("Empty calibration file for ADC " + str(channel))
376                 return False
377         return True
378
379 def loadCalibration_DAC():
380         try:
381                 input_file = log_open("calibrateDAC.dat")
382         except:
383                 print("Couldn't find calibration file for DAC")
384                 return False
385         
386         calibrate["DAC"] = []
387         for l in input_file:
388                 #print("Line is: "+str(l))
389                 l = l.split("#")[0].strip("\r\n ")
390                 if (l == ""):
391                         continue
392                 else:
393                         split_line = l.split("\t")
394                         if (len(split_line) >= 3):
395                                 calibrate["DAC"].append((int(split_line[0]), float(split_line[1]), float(split_line[2])))
396                         else:
397                                 calibrate["DAC"].append((int(split_line[0]), float(split_line[1])))
398
399
400         log_close(input_file)
401
402         if (len(calibrate["DAC"]) <= 0):
403                 print("Empty calibration file for DAC")
404                 return False
405         return True
406
407 def getADC_Voltage(channel, counts):
408         if (calibrate["ADC"][channel] == None or len(calibrate["ADC"][channel]) <= 0):
409                 if (calibrate["ADC_Rin"][channel] != None and calibrate["ADC_Rvar"][channel] != None):
410                         print("Warning: Using rough calibration for ADC"+str(channel) + " = " + str(counts))
411                         ratio = float(calibrate["ADC_Rin"][channel]) / (float(calibrate["ADC_Rin"][channel]) + float(calibrate["ADC_Rvar"][channel]))
412                         return ratio * (float(counts) / float(calibrate["ADC_Counts"][channel]) * Vref)
413                 else:
414                         print("Error: No calibration for ADC"+str(channel))
415                         return False
416
417         c = calibrate["ADC"][channel]
418         valueIndex = 1
419         for i in range(0, len(c)-1):
420                 if (c[i][0] <= counts and i + 1 < len(c)):
421                         grad = (float(c[i+1][valueIndex]) - float(c[i][valueIndex])) / (float(c[i+1][0]) - float(c[i][0]))
422                         value = float(c[i][valueIndex]) + grad * (float(counts) - float(c[i][0]))
423                         return value
424
425         
426         print("Warning: Extrapolating outside calibration range for DAC = " + str(counts))
427
428         grad = (float(c[len(c)-1][valueIndex]) - float(c[len(c)-2][valueIndex])) / (float(c[len(c)-1][0]) -  float(c[len(c)-2][0]))
429         value = float(c[len(c)-1][valueIndex]) + grad * (float(counts) - float(c[len(c)-1][0]))
430                 
431 def readADC(channel):
432         #for i in range(0, aquire["ADC_Averages"]):
433         #       ser.write("r "+str(channel)+"\r") # Send command to datalogger
434                 #time.sleep(aquire["response_wait"])
435         #       response = ser.readline().strip("#\r\n ")
436         #       if (response != "r "+str(channel)):
437         #               print("Received wierd response reading ADC ("+response+")")
438         #               return False                    
439                 #time.sleep(aquire["response_wait"])
440         #       values.append(int(ser.readline().strip("\r\n ")))
441         #       adc_sum += float(values[len(values)-1])
442         ser.write("r "+str(channel)+"\r")
443         response = ser.readline().strip("#\r\n")
444         if (response != "r "+str(channel)):
445                 print("Received wierd response reading ADC ("+response+")")
446                 return False                    
447         return ser.readline().strip("\r\n").split(" ")
448
449 def getDAC_Voltage(counts, gain = True):
450         if (calibrate["DAC"] == None or len(calibrate["DAC"]) <= 0):
451                 if (calibrate["DAC_Gain"] != None):
452                         print("Warning: Using rough calibration for DAC = " + str(counts))
453                         return float(counts) / float(calibrate["DAC_Counts"]) * float(calibrate["Vref"]) * float(calibrate["DAC_Gain"])
454                 else:
455                         print("Error: No calibrate for DAC")
456                         return False
457
458         valueIndex = 1
459         if (gain == False):
460                 valueIndex = 2
461                 if (len(calibrate["DAC"][0]) < 3):
462                         print("Error: No data for unamplified DAC")
463                         return False
464         
465         c = calibrate["DAC"]
466         if (len(c) == 1):
467                 print("Warning: Only one point in calibration data!")
468                 return float(c[0][valueIndex])
469         
470         for i in range(0, len(c)-1):
471                 if (c[i][0] <= counts and i + 1 < len(c)):
472                         grad = (float(c[i+1][valueIndex]) - float(c[i][valueIndex])) / (float(c[i+1][0]) - float(c[i][0]))
473                         value = float(c[i][valueIndex]) + grad * (float(counts) - float(c[i][0]))
474                         return value
475
476         
477         print("Warning: Extrapolating outside calibration range for DAC = " + str(counts))
478
479         grad = (float(c[len(c)-1][valueIndex]) - float(c[len(c)-2][valueIndex])) / (float(c[len(c)-1][0]) -  float(c[len(c)-2][0]))
480         value = float(c[len(c)-1][valueIndex]) + grad * (float(counts) - float(c[len(c)-1][0]))
481         return value
482
483
484 def setDAC(level):
485         
486         ser.write("d "+str(level)+"\r") 
487         
488         response = ser.readline().strip("#\r\n ")
489         if (response != "d "+str(level)):
490                 print("Recieved wierd response setting DAC to "+str(level) + " ("+response+")")
491                 return False
492         #time.sleep(aquire["response_wait"])
493         #time.sleep(aquire["DAC_Settle"])
494         #time.sleep(aquire["DAC_Settle"])
495         response = ser.readline().strip("#\r\n ")
496         if (response != "DAC "+str(level)):
497                 print("Recieved wierd response setting DAC to "+str(level) + " ("+response+")")
498                 return False
499         #time.sleep(aquire["response_wait"])
500         time.sleep(aquire["DAC_Settle"])
501                 
502
503 def calibrateADC_usingDAC(channel, gain = True):
504         if (calibrate["DAC"] == None):
505                 print("ERROR: DAC is not calibrated, can't use to calibrate ADC!")
506                 return False
507
508         calibrate["ADC"][channel] = []
509         outfile = log_open("calibrateADC"+str(channel)+".dat", "w")
510         outfile.write("# Calibrate ADC " + str(channel) + "\n")
511         outfile.write("# Start " + str(datetime.datetime.now()) + "\n")
512
513         print("Connect DAC output to ADC " + str(channel) + " and press enter\n")
514         sys.stdin.readline()
515         
516         for dac in calibrate["DAC"]:
517                 if (setDAC(dac[0]) == False):
518                         return False
519                 
520                 value = readADC(channel)
521                 if (value == False):
522                         return False
523                 canadd = (len(calibrate["ADC"][channel]) <= 0)
524                 if (canadd == False):
525                         canadd = True
526                         for adc in calibrate["ADC"][channel]:
527                                 if adc[0] == value[0]:
528                                         canadd = False
529                                         break
530
531                 if (canadd):            
532
533                         if gain:
534                                 input_value = dac[1]
535                         else:
536                                 input_value = dac[2]
537
538                         #input_value = getDAC_Voltage(dac[0], gain)
539                         if (input_value == False):
540                                 return False
541         
542                         outfile.write(str(value[0]) + "\t" + str(input_value) + "\t" + str(value[1]) + "\n")
543                         print(str(value[0]) + "\t" + str(input_value) + "\t" + str(value[1]))
544
545                         calibrate["ADC"][channel].append((value[0], input_value, value[1]))
546
547         outfile.write("# Stop " + str(datetime.datetime.now()) + "\n# EOF\n")
548         log_close(outfile)
549         if (setDAC(0) == False):
550                 return False
551         if (len(calibrate["ADC"][channel]) <= 0):
552                 print("Error: No calibration points taken for ADC " + str(channel))
553                 return False
554         return True
555
556 def calibrateADC(channel):      
557         calibrate["ADC"][channel] = []
558         outfile = log_open("calibrateADC"+str(channel)+".dat", "w")
559         outfile.write("# Calibrate ADC " + str(channel) + "\n")
560         outfile.write("# Start " + str(datetime.datetime.now()) + "\n")
561
562         print("Calibrating ADC...\n")
563         print("Enter measured voltage, empty line stops.\n")
564
565         while True:
566                 read = sys.stdin.readline().strip("\r\n ")
567                 if (read == ""):
568                         break
569                 input_value = float(read)
570                 output_value = readADC(channel)
571                 if (output_value == False):
572                         return False
573                 
574                 calibrate["ADC"][channel].append((output_value[0], input_value))
575                 print(str(output_value[0]) + "\t" + str(input_value))
576                 
577         calibrate["ADC"][channel].sort(lambda e : e[1], reverse = False)
578         
579         
580         
581
582         outfile.write("# Stop " + str(datetime.datetime.now()) + "\n# EOF\n")
583         log_close(outfile)
584         if (len(calibrate["ADC"][channel]) <= 0):
585                 print("Error: No calibration points taken for ADC " + str(channel))
586                 return False
587
588         return True
589
590 def calibrateDAC():
591         calibrate["DAC"] = []
592
593         outfile = log_open("calibrateDAC.dat", "w")
594         outfile.write("# Calibrate DAC\n")
595         outfile.write("# Start " + str(datetime.datetime.now()) + "\n")
596
597         print("Calibrating DAC...")
598         sys.stdout.write("Number of counts to increment per step: ")
599         read = sys.stdin.readline().strip("\r\n")
600         if (read == ""):
601                 return False
602         stepSize = max(1, int(read))
603         sys.stdout.write("\n")
604
605         outfile.write("# Increment by " + str(stepSize) + "\n")
606         print("Input measured DAC voltage for each level; empty input ends calibration.")
607         print("You may optionally input the DAC voltage before it is amplified too.")
608
609         level = 0
610         while (level < calibrate["DAC_Counts"]):
611
612                 setDAC(level)
613
614                 sys.stdout.write(str(level) + " ?")
615                 read = sys.stdin.readline().strip("\r\n ")
616                 if (read == ""):
617                         break
618                 else:
619                         read = read.split(" ")
620                         if (len(calibrate["DAC"]) > 0 and len(calibrate["DAC"][len(calibrate["DAC"])-1]) != len(read)):
621                                 print("Either give one value for EVERY point, or two values for EVERY point. Don't mess around.")
622                                 return False
623                         if (len(read) == 2):
624                                 calibrate["DAC"].append((level, float(read[0]), float(read[1])))
625                                 outfile.write(str(level) + "\t" + str(float(read[0])) + "\t" + str(float(read[1])) + "\n")
626                         else:
627                                 calibrate["DAC"].append((level, float(read[0])))
628                                 outfile.write(str(level) + "\t" + str(float(read[0])) + "\n")
629                 
630
631
632                 level += stepSize
633
634         outfile.write("# Stop " + str(datetime.datetime.now()) + "\n# EOF\n")
635         log_close(outfile)
636         if (len(calibrate["DAC"]) <= 0):
637                 print("Error: No calibration points taken for DAC")
638                 return False
639         return setDAC(0)
640
641 # Run the main function
642 if __name__ == "__main__":
643         sys.exit(main())

UCC git Repository :: git.ucc.asn.au