From 9b3e7c71d8609f4a6f366b35b0789aa3f9faee26 Mon Sep 17 00:00:00 2001 From: "Michael D. M. Dryden" Date: Fri, 28 Nov 2014 17:22:37 -0500 Subject: [PATCH] Implements Potentiometry and OCP. Completes #18. --- dstat-interface/dstat-interface/dstat_comm.py | 59 +++++++++- .../interface/dstatinterface.glade | 22 +++- .../dstat-interface/interface/exp_int.py | 8 ++ .../dstat-interface/interface/exp_window.py | 1 + .../dstat-interface/interface/potexp.glade | 102 ++++++++++++++++++ dstat-interface/dstat-interface/main.py | 97 ++++++++++++++++- 6 files changed, 285 insertions(+), 4 deletions(-) create mode 100644 dstat-interface/dstat-interface/interface/potexp.glade diff --git a/dstat-interface/dstat-interface/dstat_comm.py b/dstat-interface/dstat-interface/dstat_comm.py index 426acfa..4a400a3 100644 --- a/dstat-interface/dstat-interface/dstat_comm.py +++ b/dstat-interface/dstat-interface/dstat_comm.py @@ -44,14 +44,18 @@ def version_check(ser_port): Arguments: ser_port -- address of serial port to use """ + ser = delayedSerial(ser_port, 1024000, timeout=1) ser.write("ck") ser.flushInput() ser.write('!') - while not ser.read().startswith("C"): + while not ser.read()=="C": + time.sleep(.5) ser.write('!') + + ser.write('V') for line in ser: if line.startswith('V'): @@ -158,6 +162,7 @@ class Experiment(object): self.serial.write(i) if not self.serial_handler(): + self.main_pipe.send("ABORT") break self.data_postprocessing() @@ -174,13 +179,16 @@ class Experiment(object): if self.main_pipe.poll(): if self.main_pipe.recv() == 'a': self.serial.write('a') + print "ABORT!" return False for line in self.serial: if self.main_pipe.poll(): if self.main_pipe.recv() == 'a': self.serial.write('a') + print "ABORT!" return False + if line.startswith('B'): self.main_pipe.send(self.data_handler( (scan, self.serial.read(size=self.databytes)))) @@ -245,6 +253,32 @@ class Chronoamp(Experiment): return (scan, [seconds+milliseconds/1000., current*(1.5/self.gain/8388607)]) +class PotExp(Experiment): + """Potentiometry experiment""" + def __init__(self, parameters, main_pipe): + super(PotExp, self).__init__(parameters, main_pipe) + + self.datatype = "linearData" + self.xlabel = "Time (s)" + self.ylabel = "Voltage (V)" + self.data = [[], []] + self.datalength = 2 + self.databytes = 8 + self.xmin = 0 + self.xmax = self.parameters['time'] + + self.commands += "P" + self.commands[2] += str(self.parameters['time']) + self.commands[2] += " 1 " #potentiometry mode + + def data_handler(self, data_input): + """Overrides Experiment method to not convert x axis to mV.""" + scan, data = data_input + # 2*uint16 + int32 + seconds, milliseconds, voltage = struct.unpack('pde Photodiode + + 7 + pot + Potentiometry + @@ -1225,6 +1230,21 @@ Thanks to Christian Fobel for help with Dropbot Plugin 3 + + + True + False + 0 + 5 + OCP: + True + + + False + False + 4 + + True @@ -1234,7 +1254,7 @@ Thanks to Christian Fobel for help with Dropbot Plugin True True - 4 + 5 diff --git a/dstat-interface/dstat-interface/interface/exp_int.py b/dstat-interface/dstat-interface/interface/exp_int.py index 8e2e01c..2e67a1e 100644 --- a/dstat-interface/dstat-interface/interface/exp_int.py +++ b/dstat-interface/dstat-interface/interface/exp_int.py @@ -201,4 +201,12 @@ class PD(ExpInterface): super(PD, self).__init__('interface/pd.glade') self.entry['voltage'] = self.builder.get_object('voltage_entry') + self.entry['time'] = self.builder.get_object('time_entry') + +class POT(ExpInterface): + """Experiment class for Potentiometry.""" + def __init__(self): + """Adds entry listings to superclass's self.entry dict""" + super(POT, self).__init__('interface/potexp.glade') + self.entry['time'] = self.builder.get_object('time_entry') \ No newline at end of file diff --git a/dstat-interface/dstat-interface/interface/exp_window.py b/dstat-interface/dstat-interface/interface/exp_window.py index 79301eb..f5bf561 100644 --- a/dstat-interface/dstat-interface/interface/exp_window.py +++ b/dstat-interface/dstat-interface/interface/exp_window.py @@ -31,6 +31,7 @@ class Experiments: self.classes['dpv'] = exp.DPV() self.classes['acv'] = exp.ACV() self.classes['pde'] = exp.PD() + self.classes['pot'] = exp.POT() #fill exp_section exp_section = self.builder.get_object('exp_section_box') diff --git a/dstat-interface/dstat-interface/interface/potexp.glade b/dstat-interface/dstat-interface/interface/potexp.glade new file mode 100644 index 0000000..4d8d915 --- /dev/null +++ b/dstat-interface/dstat-interface/interface/potexp.glade @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + False + 300 + 500 + + + True + True + automatic + automatic + + + True + False + none + + + True + False + + + True + False + 2 + 2 + + + True + False + Time (s) + + + 2 + + + + + True + True + + 5 + 0 + 1 + True + True + False + False + False + True + True + + + 1 + 2 + 2 + GTK_EXPAND + GTK_SHRINK + + + + + False + True + 5 + 0 + + + + + True + False + Connect the electrodes to the RE input and the W_SHIELD connectors. +The ADC's PGA can be used to amplify the input signal, but note that the plot's y-axis is only correct for PGA 2x. + True + 30 + + + True + True + 1 + + + + + + + + + + diff --git a/dstat-interface/dstat-interface/main.py b/dstat-interface/dstat-interface/main.py index 9425a42..94d91d2 100644 --- a/dstat-interface/dstat-interface/main.py +++ b/dstat-interface/dstat-interface/main.py @@ -60,6 +60,7 @@ class Main(object): #create instance of interface components self.statusbar = self.builder.get_object('statusbar') + self.ocp_disp = self.builder.get_object('ocp_disp') self.window = self.builder.get_object('window1') self.aboutdialog = self.builder.get_object('aboutdialog1') self.rawbuffer = self.builder.get_object('databuffer1') @@ -125,10 +126,12 @@ class Main(object): def on_window1_destroy(self, object, data=None): """ Quit when main window closed.""" + self.on_pot_stop_clicked() gtk.main_quit() def on_gtk_quit_activate(self, menuitem, data=None): """Quit when Quit selected from menu.""" + self.on_pot_stop_clicked() gtk.main_quit() def on_gtk_about_activate(self, menuitem, data=None): @@ -155,6 +158,11 @@ class Main(object): def on_serial_version_clicked(self, data=None): """Retrieve DStat version.""" + try: + self.on_pot_stop_clicked() + except AttributeError: + pass + self.version = comm.version_check(self.serial_liststore.get_value( self.serial_combobox.get_active_iter(), 0)) @@ -170,6 +178,23 @@ class Main(object): "".join(["DStat version: ", str(self.version[0]), ".", str(self.version[1])]) ) + self.start_ocp() + + def start_ocp(self): + """Start OCP measurements.""" + if self.version[0] >= 1 and self.version[1] >= 2: + self.recv_p, self.send_p = multiprocessing.Pipe(duplex=True) + self.current_exp = comm.OCPExp(self.send_p) + + self.current_exp.run_wrapper(self.serial_liststore.get_value( + self.serial_combobox.get_active_iter(), 0)) + + self.send_p.close() # need for EOF signal to work + + self.ocp_proc = gobject.idle_add(self.ocp_running) + else: + print "OCP measurements not supported on v1.1 boards." + return def on_pot_start_clicked(self, data=None): """Run currently visible experiment.""" @@ -185,7 +210,12 @@ class Main(object): self.spinner.stop() self.startbutton.set_sensitive(True) self.stopbutton.set_sensitive(False) - + self.start_ocp() + + # Stop OCP measurements + self.on_pot_stop_clicked() + gobject.source_remove(self.ocp_proc) + selection = self.expcombobox.get_active() parameters = {} parameters['version'] = self.version @@ -467,7 +497,38 @@ class Main(object): self.experiment_running_plot) gobject.idle_add(self.experiment_running) return + elif selection == 7: # POT + if not (self.version[0] >= 1 and self.version[1] >= 2): + self.statusbar.push(self.error_context_id, + "v1.1 board does not support potentiometry.") + exceptions() + return + + parameters.update(self.exp_window.get_params('pot')) + + if (parameters['time'] <= 0): + raise InputError(parameters['clean_s'], + "Time must be greater than zero.") + if (parameters['time'] > 65535): + raise InputError(parameters['clean_s'], + "Time must fit in 16-bit counter.") + + self.recv_p, self.send_p = multiprocessing.Pipe(duplex=True) + self.current_exp = comm.PotExp(parameters, self.send_p) + + self.plot.clearall() + self.plot.changetype(self.current_exp) + + self.current_exp.run_wrapper( + self.serial_liststore.get_value( + self.serial_combobox.get_active_iter(), 0)) + + self.send_p.close() + self.plot_proc = gobject.timeout_add(200, + self.experiment_running_plot) + gobject.idle_add(self.experiment_running) + return else: self.statusbar.push(self.error_context_id, "Experiment not yet implemented.") @@ -523,6 +584,29 @@ class Main(object): self.experiment_done() return False + def ocp_running(self): + """Receive OCP value from experiment process and update ocp_disp field + + Returns: + True -- when experiment is continuing to keep function in GTK's queue. + False -- when experiment process signals EOFError or IOError to remove + function from GTK's queue. + """ + try: + if self.recv_p.poll(): + data = "".join(["OCP: ", + "{0:.3f}".format(self.recv_p.recv()), + " V"]) + self.ocp_disp.set_text(data) + + else: + time.sleep(.001) + return True + except EOFError: + return False + except IOError: + return False + def experiment_running_plot(self): """Plot all data in current_exp.data. Run in GTK main loop. Always returns True so must be manually @@ -582,12 +666,21 @@ class Main(object): self.spinner.stop() self.startbutton.set_sensitive(True) self.stopbutton.set_sensitive(False) + self.start_ocp() def on_pot_stop_clicked(self, data=None): """Stop current experiment. Signals experiment process to stop.""" - if self.recv_p: + try: print "stop" self.recv_p.send('a') + while True: + if self.recv_p.poll(): + if self.recv_p.recv() == "ABORT": + return + except AttributeError: + pass + except IOError: + pass def on_file_save_exp_activate(self, menuitem, data=None): """Activate dialogue to save current experiment data. """ -- GitLab