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

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