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

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