Skip to content
Snippets Groups Projects
Commit 9b3e7c71 authored by Michael DM Dryden's avatar Michael DM Dryden
Browse files

Implements Potentiometry and OCP. Completes #18.

parent 318326da
Branches
Tags
No related merge requests found
......@@ -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('<HHl', data)
return (scan,
[seconds+milliseconds/1000., voltage*(1.5/8388607.)])
class LSVExp(Experiment):
"""Linear Scan Voltammetry experiment"""
def __init__(self, parameters, main_pipe):
......@@ -406,3 +440,26 @@ class DPVExp(SWVExp):
self.commands[2] += " "
self.commands[2] += str(self.parameters['width'])
self.commands[2] += " "
class OCPExp(Experiment):
"""Open circuit potential measumement in statusbar."""
def __init__(self, main_pipe):
"""Only needs data pipe."""
self.main_pipe = main_pipe
self.databytes = 8
self.commands = ["A", "P"]
self.commands[0] += "2 " # input buffer
self.commands[0] += "3 " # 2.5 Hz sample rate
self.commands[0] += "1 " # 2x PGA
self.commands[1] += "0 " # no timeout
self.commands[1] += "0 " # OCP measurement mode
def data_handler(self, data_input):
"""Overrides Experiment method to only send ADC values."""
scan, data = data_input
# 2*uint16 + int32
seconds, milliseconds, voltage = struct.unpack('<HHl', data)
return (voltage/5.592405e6)
\ No newline at end of file
......@@ -47,6 +47,11 @@
<col id="1" translatable="yes">pde</col>
<col id="2" translatable="yes">Photodiode</col>
</row>
<row>
<col id="0">7</col>
<col id="1" translatable="yes">pot</col>
<col id="2" translatable="yes">Potentiometry</col>
</row>
</data>
</object>
<object class="GtkAboutDialog" id="aboutdialog1">
......@@ -1225,6 +1230,21 @@ Thanks to Christian Fobel for help with Dropbot Plugin</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ocp_disp">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">5</property>
<property name="label" translatable="yes">OCP:</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkStatusbar" id="statusbar">
<property name="visible">True</property>
......@@ -1234,7 +1254,7 @@ Thanks to Christian Fobel for help with Dropbot Plugin</property>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
<property name="position">5</property>
</packing>
</child>
</object>
......
......@@ -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
......@@ -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')
......
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.24"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkListStore" id="ca_list">
<columns>
<!-- column-name millivolts -->
<column type="gint"/>
<!-- column-name seconds -->
<column type="guint"/>
</columns>
</object>
<object class="GtkWindow" id="window1">
<property name="can_focus">False</property>
<property name="default_width">300</property>
<property name="default_height">500</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkViewport" id="viewport1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Time (s)</property>
</object>
<packing>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="time_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char"></property>
<property name="width_chars">5</property>
<property name="text" translatable="yes">0</property>
<property name="xalign">1</property>
<property name="truncate_multiline">True</property>
<property name="invisible_char_set">True</property>
<property name="caps_lock_warning">False</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_EXPAND</property>
<property name="y_options">GTK_SHRINK</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">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.</property>
<property name="wrap">True</property>
<property name="width_chars">30</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
......@@ -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. """
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment