diff --git a/.gitignore b/.gitignore index e6133b777200ab3b5af5cebedc528094037d5869..bc7453473705cbaf6a772e13df5d45c1d3891700 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ Breakpoints_v2.xcbkptlist *.c *.so *.pyc +*.pyo *~ /dstatInterface/dist/ -/dstatInterface/build/ \ No newline at end of file +/dstatInterface/build/ +/dstat-interface/dstat-interface/dist/ +/dstat-interface/dstat-interface/build/ \ No newline at end of file diff --git a/dstatInterface/interface/__init__.py b/dstat-interface/CHANGELOG similarity index 100% rename from dstatInterface/interface/__init__.py rename to dstat-interface/CHANGELOG diff --git a/dstat-interface/LICENSE b/dstat-interface/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/dstat-interface/README b/dstat-interface/README new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/dstat-interface/dstat-interface.psp b/dstat-interface/dstat-interface.psp new file mode 100644 index 0000000000000000000000000000000000000000..b18557a8d05adb4378a09e8f25d7f2afd2856190 --- /dev/null +++ b/dstat-interface/dstat-interface.psp @@ -0,0 +1,6 @@ +<?xml version="1.0" ?> +<project name="dstat-interface" version="1.0"> + <optionset name="general"/> + <package name="dstat-interface"/> + <folder name="tests"/> +</project> diff --git a/dstat-interface/dstat-interface/__init__.py b/dstat-interface/dstat-interface/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/dstatInterface/build_windows.py b/dstat-interface/dstat-interface/build_windows.py similarity index 89% rename from dstatInterface/build_windows.py rename to dstat-interface/dstat-interface/build_windows.py index b5377bea146eaebd2484b376dc8d5c9030973922..14d1fbda70190e8c7c19bacc30e58a5891b6897c 100644 --- a/dstatInterface/build_windows.py +++ b/dstat-interface/dstat-interface/build_windows.py @@ -5,7 +5,7 @@ __requires__ = 'PyInstaller==2.1' import os, sys os.chdir(os.path.dirname(sys.argv[0])) -args = ['interface_test.spec'] +args = ['dstat.spec'] args.extend(sys.argv[1:]) import PyInstaller.main as pyi #For some reason, it gets the path here, so working dir must be set first diff --git a/dstatInterface/drivers/VirtualSerial.inf b/dstat-interface/dstat-interface/drivers/VirtualSerial.inf similarity index 100% rename from dstatInterface/drivers/VirtualSerial.inf rename to dstat-interface/dstat-interface/drivers/VirtualSerial.inf diff --git a/dstat-interface/dstat-interface/dstat.spec b/dstat-interface/dstat-interface/dstat.spec new file mode 100644 index 0000000000000000000000000000000000000000..c0f001d40d21cd853351472898e4aee887784fbb --- /dev/null +++ b/dstat-interface/dstat-interface/dstat.spec @@ -0,0 +1,25 @@ +# -*- mode: python -*- +a = Analysis(['./main.py'], + pathex=['/Users/mdryden/src/dstat-interface2/dstatInterface'], + hiddenimports=[], + hookspath=None, + runtime_hooks=None) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=True, + name='DStat', + debug=False, + strip=None, + upx=True, + console=False ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=None, + upx=True, + name='DStat') +app = BUNDLE(coll, + name='DStat.app', + icon=None) diff --git a/dstatInterface/dstatInterface.xcodeproj/project.pbxproj b/dstat-interface/dstat-interface/dstatInterface.xcodeproj/project.pbxproj similarity index 100% rename from dstatInterface/dstatInterface.xcodeproj/project.pbxproj rename to dstat-interface/dstat-interface/dstatInterface.xcodeproj/project.pbxproj diff --git a/dstatInterface/dstatInterface.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/dstat-interface/dstat-interface/dstatInterface.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from dstatInterface/dstatInterface.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to dstat-interface/dstat-interface/dstatInterface.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/dstatInterface/dstatInterface.xcodeproj/project.xcworkspace/xcshareddata/dstatInterface.xccheckout b/dstat-interface/dstat-interface/dstatInterface.xcodeproj/project.xcworkspace/xcshareddata/dstatInterface.xccheckout similarity index 100% rename from dstatInterface/dstatInterface.xcodeproj/project.xcworkspace/xcshareddata/dstatInterface.xccheckout rename to dstat-interface/dstat-interface/dstatInterface.xcodeproj/project.xcworkspace/xcshareddata/dstatInterface.xccheckout diff --git a/dstatInterface/dstatInterface.xcodeproj/xcuserdata/mdryden.xcuserdatad/xcschemes/dstatInterface.xcscheme b/dstat-interface/dstat-interface/dstatInterface.xcodeproj/xcuserdata/mdryden.xcuserdatad/xcschemes/dstatInterface.xcscheme similarity index 100% rename from dstatInterface/dstatInterface.xcodeproj/xcuserdata/mdryden.xcuserdatad/xcschemes/dstatInterface.xcscheme rename to dstat-interface/dstat-interface/dstatInterface.xcodeproj/xcuserdata/mdryden.xcuserdatad/xcschemes/dstatInterface.xcscheme diff --git a/dstatInterface/dstatInterface.xcodeproj/xcuserdata/mdryden.xcuserdatad/xcschemes/xcschememanagement.plist b/dstat-interface/dstat-interface/dstatInterface.xcodeproj/xcuserdata/mdryden.xcuserdatad/xcschemes/xcschememanagement.plist similarity index 100% rename from dstatInterface/dstatInterface.xcodeproj/xcuserdata/mdryden.xcuserdatad/xcschemes/xcschememanagement.plist rename to dstat-interface/dstat-interface/dstatInterface.xcodeproj/xcuserdata/mdryden.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/dstatInterface/dstat_comm.py b/dstat-interface/dstat-interface/dstat_comm.py similarity index 62% rename from dstatInterface/dstat_comm.py rename to dstat-interface/dstat-interface/dstat_comm.py index 69ceb4cd69296412536a60d8e5b9d96f15b08aca..ff4dab024f5949b782e6625d80b2dce11b388985 100644 --- a/dstatInterface/dstat_comm.py +++ b/dstat-interface/dstat-interface/dstat_comm.py @@ -1,26 +1,36 @@ #!/usr/bin/env python -import serial, io, time, struct, sys, os -from types import * +import serial from serial.tools import list_ports -import numpy as np +import time +import struct import multiprocessing as mp -from Queue import Empty def call_it(instance, name, args=(), kwargs=None): - "indirect caller for instance methods and multiprocessing" + """Indirect caller for instance methods and multiprocessing. + + Arguments: + instance -- instance to which the method belongs + name -- method to call + args -- passed to method + kwargs -- passed to method + """ if kwargs is None: kwargs = {} return getattr(instance, name)(*args, **kwargs) -class delayedSerial(serial.Serial): #overrides normal serial write so that characters are output individually with a slight delay +class delayedSerial(serial.Serial): + """Extends Serial.write so that characters are output individually + with a slight delay + """ def write(self, data): for i in data: serial.Serial.write(self, i) time.sleep(.001) -class SerialDevices: +class SerialDevices(object): + """Retrieves and stores list of serial devices in self.ports""" def __init__(self): try: self.ports, _, _ = zip(*list_ports.comports()) @@ -29,22 +39,33 @@ class SerialDevices: print "No serial ports found" def refresh(self): + """Refreshes list of ports.""" self.ports, _, _ = zip(*list_ports.comports()) -class Experiment: +class Experiment(object): + """Store and acquire a potentiostat experiment. Meant to be subclassed + to by different experiment types and not used instanced directly. + """ def run_wrapper(self, *argv): - self.p = mp.Process(target=call_it, args=(self, 'run', argv)) - self.p.start() + """Execute experiment indirectly using call_it to bypass lack of fork() + on Windows for multiprocessing. + """ + self.proc = mp.Process(target=call_it, args=(self, 'run', argv)) + self.proc.start() - def __init__(self): #will always be overriden, but self.parameters, self.viewparameters, and self.databytes should be defined - pass + def __init__(self, parameters, main_pipe): + """Must be overridden to define self.parameters, and self.databytes.""" + self.parameters = parameters + self.main_pipe = main_pipe + self.databytes = 8 def init(self): - self.data_extra = [] #must be defined even when not needed + """Adds commands for gain and ADC.""" + self.data_extra = [] # must be defined even when not needed self.__gaintable = [1e2, 3e2, 3e3, 3e4, 3e5, 3e6, 3e7, 5e8] self.gain = self.__gaintable[int(self.parameters['gain'])] - self.commands = ["A","G"] + self.commands = ["A", "G"] self.commands[0] += (self.parameters['adc_buffer']) self.commands[0] += " " @@ -55,8 +76,15 @@ class Experiment: self.commands[1] += (self.parameters['gain']) self.commands[1] += " " - def run(self, strPort): - self.serial = delayedSerial(strPort, 1024000, timeout=1) + def run(self, ser_port): + """Execute experiment. Connects and sends handshake signal to DStat + then sendsself.commands. Don't call directly as a process in Windows, + use run_wrapper instead. + + Arguments: + ser_port -- address of serial port to use + """ + self.serial = delayedSerial(ser_port, 1024000, timeout=1) self.serial.write("ck") self.serial.flushInput() @@ -77,6 +105,10 @@ class Experiment: self.main_pipe.close() def serial_handler(self): + """Handles incoming serial transmissions from DStat. Returns False + if stop button pressed and sends abort signal to instrument. Sends + data to self.main_pipe as result of self.data_handler). + """ scan = 0 while True: if self.main_pipe.poll(): @@ -87,7 +119,8 @@ class Experiment: for line in self.serial: if line.startswith('B'): - self.main_pipe.send(self.data_handler((scan, self.serial.read(size=self.databytes)))) + self.main_pipe.send(self.data_handler( + (scan, self.serial.read(size=self.databytes)))) elif line.startswith('S'): scan += 1 elif line.startswith("#"): @@ -98,22 +131,31 @@ class Experiment: return True - def data_handler(self, input): - scan, data = input + def data_handler(self, data_input): + """Takes data_input as tuple -- (scan, data). + Returns: + (scan number, [voltage, current]) -- voltage in mV, current in A + """ + scan, data = data_input voltage, current = struct.unpack('<Hl', data) #uint16 + int32 - return (scan, [(voltage-32768)*3000./65536, current*(1.5/self.gain/8388607)]) + return (scan, + [(voltage-32768)*3000./65536, current*(1.5/self.gain/8388607)]) def data_postprocessing(self): + """No data postprocessing done by default, can be overridden + in subclass. + """ pass class chronoamp(Experiment): + """Chronoamperometry experiment""" def __init__(self, parameters, main_pipe): self.main_pipe = main_pipe self.parameters = parameters self.datatype = "linearData" self.xlabel = "Time (s)" self.ylabel = "Current (A)" - self.data = [[],[]] + self.data = [[], []] self.datalength = 2 self.databytes = 8 self.xmin = 0 @@ -122,7 +164,7 @@ class chronoamp(Experiment): for i in self.parameters['time']: self.xmax += int(i) - self.init() #need to call after xmin and xmax are set + self.init() # need to call after xmin and xmax are set self.commands += "R" self.commands[2] += str(len(self.parameters['potential'])) @@ -134,12 +176,16 @@ class chronoamp(Experiment): self.commands[2] += str(i) self.commands[2] += " " - def data_handler(self, input): #overrides inherited method to not convert x axis - scan, data = input - seconds, milliseconds, current = struct.unpack('<HHl', data) #2*uint16 + int32 - return (scan, [seconds+milliseconds/1000., current*(1.5/self.gain/8388607)]) + 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, current = struct.unpack('<HHl', data) + return (scan, + [seconds+milliseconds/1000., current*(1.5/self.gain/8388607)]) class lsv_exp(Experiment): + """Linear Scan Voltammetry experiment""" def __init__(self, parameters, send_pipe): self.main_pipe = send_pipe self.parameters = parameters @@ -147,22 +193,24 @@ class lsv_exp(Experiment): self.datatype = "linearData" self.xlabel = "Voltage (mV)" self.ylabel = "Current (A)" - self.data = [[],[]] + self.data = [[], []] self.datalength = 2 - self.databytes = 6 #uint16 + int32 + self.databytes = 6 # uint16 + int32 self.xmin = self.parameters['start'] self.xmax = self.parameters['stop'] - self.init() #need to call after xmin and xmax are set + self.init() # need to call after xmin and xmax are set self.commands += "L" self.commands[2] += str(self.parameters['clean_s']) self.commands[2] += " " self.commands[2] += str(self.parameters['dep_s']) self.commands[2] += " " - self.commands[2] += str(int(self.parameters['clean_mV']*(65536./3000)+32768)) + self.commands[2] += str(int(self.parameters['clean_mV']* + (65536./3000)+32768)) self.commands[2] += " " - self.commands[2] += str(int(self.parameters['dep_mV']*(65536./3000)+32768)) + self.commands[2] += str(int(self.parameters['dep_mV']* + (65536./3000)+32768)) self.commands[2] += " " self.commands[2] += str(self.parameters['start']) self.commands[2] += " " @@ -172,6 +220,7 @@ class lsv_exp(Experiment): self.commands[2] += " " class cv_exp(Experiment): + """Cyclic Voltammetry experiment""" def __init__(self, parameters, main_pipe): self.main_pipe = main_pipe self.parameters = parameters @@ -179,9 +228,9 @@ class cv_exp(Experiment): self.datatype = "CVData" self.xlabel = "Voltage (mV)" self.ylabel = "Current (A)" - self.data = [[],[]] #Will have to alter data_handler to add new lists as needed - self.datalength = 2 * self.parameters['scans'] #x and y for each scan - self.databytes = 6 #uint16 + int32 + self.data = [[], []] + self.datalength = 2 * self.parameters['scans'] # x and y for each scan + self.databytes = 6 # uint16 + int32 self.xmin = self.parameters['v1'] self.xmax = self.parameters['v2'] @@ -192,9 +241,11 @@ class cv_exp(Experiment): self.commands[2] += " " self.commands[2] += str(self.parameters['dep_s']) self.commands[2] += " " - self.commands[2] += str(int(self.parameters['clean_mV']*(65536./3000)+32768)) + self.commands[2] += str(int(self.parameters['clean_mV']* + (65536./3000)+32768)) self.commands[2] += " " - self.commands[2] += str(int(self.parameters['dep_mV']*(65536./3000)+32768)) + self.commands[2] += str(int(self.parameters['dep_mV']* + (65536./3000)+32768)) self.commands[2] += " " self.commands[2] += str(self.parameters['v1']) self.commands[2] += " " @@ -206,13 +257,9 @@ class cv_exp(Experiment): self.commands[2] += " " self.commands[2] += str(self.parameters['slope']) self.commands[2] += " " - - def data_handler(self, input): - scan, data = input - voltage, current = struct.unpack('<Hl', data) #uint16 + int32 - return (scan, [(voltage-32768)*3000./65536, current*(1.5/self.gain/8388607)]) class swv_exp(Experiment): + """Square Wave Voltammetry experiment""" def __init__(self, parameters, main_pipe): self.main_pipe = main_pipe self.parameters = parameters @@ -220,7 +267,7 @@ class swv_exp(Experiment): self.datatype = "SWVData" self.xlabel = "Voltage (mV)" self.ylabel = "Current (A)" - self.data = [[],[]] #only difference stored here + self.data = [[], []] # only difference stored here self.datalength = 2 * self.parameters['scans'] self.databytes = 10 @@ -228,16 +275,20 @@ class swv_exp(Experiment): self.xmax = self.parameters['stop'] self.init() - self.data_extra = [[],[]] #forward/reverse stored here - needs to be after self.init to keep from being redefined + # forward/reverse stored here - needs to be after + # self.init to keep from being redefined + self.data_extra = [[], []] self.commands += "S" self.commands[2] += str(self.parameters['clean_s']) self.commands[2] += " " self.commands[2] += str(self.parameters['dep_s']) self.commands[2] += " " - self.commands[2] += str(int(self.parameters['clean_mV']*(65536./3000)+32768)) + self.commands[2] += str(int(self.parameters['clean_mV']* + (65536./3000)+32768)) self.commands[2] += " " - self.commands[2] += str(int(self.parameters['dep_mV']*(65536./3000)+32768)) + self.commands[2] += str(int(self.parameters['dep_mV']* + (65536./3000)+32768)) self.commands[2] += " " self.commands[2] += str(self.parameters['start']) self.commands[2] += " " @@ -252,21 +303,28 @@ class swv_exp(Experiment): self.commands[2] += str(self.parameters['scans']) self.commands[2] += " " - def data_handler(self, input): - scan, data = input - voltage, forward, reverse = struct.unpack('<Hll', data) #uint16 + int32 - return (scan, [(voltage-32768)*3000./65536, (forward-reverse)*(1.5/self.gain/8388607), forward*(1.5/self.gain/8388607), reverse*(1.5/self.gain/8388607)]) + def data_handler(self, input_data): + """Overrides Experiment method to calculate difference current""" + scan, data = input_data + # uint16 + int32 + voltage, forward, reverse = struct.unpack('<Hll', data) + return (scan, [(voltage-32768)*3000./65536, + (forward-reverse)*(1.5/self.gain/8388607), + forward*(1.5/self.gain/8388607), + reverse*(1.5/self.gain/8388607)]) class dpv_exp(swv_exp): + """Diffential Pulse Voltammetry experiment.""" def __init__(self, parameters, main_pipe): + """Overrides swv_exp method""" self.main_pipe = main_pipe self.parameters = parameters self.datatype = "SWVData" self.xlabel = "Voltage (mV)" self.ylabel = "Current (A)" - self.data = [[],[]] #only difference stored here + self.data = [[], []] # only difference stored here self.datalength = 2 self.databytes = 10 @@ -274,16 +332,20 @@ class dpv_exp(swv_exp): self.xmax = self.parameters['stop'] self.init() - self.data_extra = [[],[]] #forward/reverse stored here - needs to be after self.init to keep from being redefined + # forward/reverse stored here - needs to be after self.init to + # keep from being redefined + self.data_extra = [[], []] self.commands += "D" self.commands[2] += str(self.parameters['clean_s']) self.commands[2] += " " self.commands[2] += str(self.parameters['dep_s']) self.commands[2] += " " - self.commands[2] += str(int(self.parameters['clean_mV']*(65536./3000)+32768)) + self.commands[2] += str(int(self.parameters['clean_mV']* + (65536./3000)+32768)) self.commands[2] += " " - self.commands[2] += str(int(self.parameters['dep_mV']*(65536./3000)+32768)) + self.commands[2] += str(int(self.parameters['dep_mV']* + (65536./3000)+32768)) self.commands[2] += " " self.commands[2] += str(self.parameters['start']) self.commands[2] += " " diff --git a/dstat-interface/dstat-interface/interface/__init__.py b/dstat-interface/dstat-interface/interface/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/dstatInterface/interface/acv.glade b/dstat-interface/dstat-interface/interface/acv.glade similarity index 100% rename from dstatInterface/interface/acv.glade rename to dstat-interface/dstat-interface/interface/acv.glade diff --git a/dstatInterface/interface/adc_pot.glade b/dstat-interface/dstat-interface/interface/adc_pot.glade similarity index 100% rename from dstatInterface/interface/adc_pot.glade rename to dstat-interface/dstat-interface/interface/adc_pot.glade diff --git a/dstatInterface/interface/adc_pot.py b/dstat-interface/dstat-interface/interface/adc_pot.py similarity index 100% rename from dstatInterface/interface/adc_pot.py rename to dstat-interface/dstat-interface/interface/adc_pot.py diff --git a/dstatInterface/interface/chronoamp.glade b/dstat-interface/dstat-interface/interface/chronoamp.glade similarity index 100% rename from dstatInterface/interface/chronoamp.glade rename to dstat-interface/dstat-interface/interface/chronoamp.glade diff --git a/dstatInterface/interface/cv.glade b/dstat-interface/dstat-interface/interface/cv.glade similarity index 100% rename from dstatInterface/interface/cv.glade rename to dstat-interface/dstat-interface/interface/cv.glade diff --git a/dstatInterface/interface/dpv.glade b/dstat-interface/dstat-interface/interface/dpv.glade similarity index 100% rename from dstatInterface/interface/dpv.glade rename to dstat-interface/dstat-interface/interface/dpv.glade diff --git a/dstatInterface/interface/dstatinterface.glade b/dstat-interface/dstat-interface/interface/dstatinterface.glade similarity index 96% rename from dstatInterface/interface/dstatinterface.glade rename to dstat-interface/dstat-interface/interface/dstatinterface.glade index 6b1062a172816a5543303c918182dd7edbdf0d5a..511429af2f81c175fec9fc4d113e85235cd7cf5e 100644 --- a/dstatInterface/interface/dstatinterface.glade +++ b/dstat-interface/dstat-interface/interface/dstatinterface.glade @@ -6,37 +6,46 @@ <columns> <!-- column-name index --> <column type="guint"/> + <!-- column-name id --> + <column type="gchararray"/> <!-- column-name name --> <column type="gchararray"/> </columns> <data> <row> <col id="0">0</col> - <col id="1" translatable="yes">Chronoamperometry</col> + <col id="1" translatable="yes">cae</col> + <col id="2" translatable="yes">Chronoamperometry</col> </row> <row> <col id="0">1</col> - <col id="1" translatable="yes">Linear Sweep Voltammetry</col> + <col id="1" translatable="yes">lsv</col> + <col id="2" translatable="yes">Linear Sweep Voltammetry</col> </row> <row> <col id="0">2</col> - <col id="1" translatable="yes">Cyclic Voltammetry</col> + <col id="1" translatable="yes">cve</col> + <col id="2" translatable="yes">Cyclic Voltammetry</col> </row> <row> <col id="0">3</col> - <col id="1" translatable="yes">Square Wave Voltammetry</col> + <col id="1" translatable="yes">swv</col> + <col id="2" translatable="yes">Square Wave Voltammetry</col> </row> <row> <col id="0">4</col> - <col id="1" translatable="yes">Differential Pulse Voltammetry</col> + <col id="1" translatable="yes">dpv</col> + <col id="2" translatable="yes">Differential Pulse Voltammetry</col> </row> <row> <col id="0">5</col> - <col id="1" translatable="yes">AC Voltammetry</col> + <col id="1" translatable="yes">acv</col> + <col id="2" translatable="yes">AC Voltammetry</col> </row> <row> <col id="0">6</col> - <col id="1" translatable="yes">Photodiode</col> + <col id="1" translatable="yes">pde</col> + <col id="2" translatable="yes">Photodiode</col> </row> </data> </object> @@ -188,6 +197,7 @@ <property name="label">gtk-connect</property> <property name="visible">True</property> <property name="can_focus">False</property> + <property name="use_underline">True</property> <property name="use_stock">True</property> <signal name="activate" handler="on_menu_dropbot_connect_activate" swapped="no"/> </object> @@ -198,6 +208,7 @@ <property name="visible">True</property> <property name="sensitive">False</property> <property name="can_focus">False</property> + <property name="use_underline">True</property> <property name="use_stock">True</property> <signal name="activate" handler="on_menu_dropbot_disconnect_activate" swapped="no"/> </object> diff --git a/dstat-interface/dstat-interface/interface/exp_int.py b/dstat-interface/dstat-interface/interface/exp_int.py new file mode 100644 index 0000000000000000000000000000000000000000..8208eeb7d7cba6127dc71e8daac54065aaf25c4a --- /dev/null +++ b/dstat-interface/dstat-interface/interface/exp_int.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python + +import gtk + +class ExpInterface(object): + """Generic experiment interface class. Should be subclassed to implement + experiment interfaces by populating self.entry. + + Public methods: + get_params(self) + """ + def __init__(self, glade_path): + self.builder = gtk.Builder() + self.builder.add_from_file(glade_path) + self.builder.connect_signals(self) + self.entry = {} + + def get_params(self): + """Returns a dict of parameters for experiment.""" + parameters = {} + for key, value in self.entry.iteritems(): + parameters[key] = int(value.get_text()) + return parameters + +class Chronoamp(ExpInterface): + """Experiment class for chronoamperometry. Extends ExpInterface class to + support treeview neeeded for CA. + + Public methods: + on_add_button_clicked(self, widget) + on_remove_button_clicked(self, widget) + get_params(self) + """ + def __init__(self): + """Extends superclass method to support treeview.""" + super(Chronoamp, self).__init__('interface/chronoamp.glade') + + self.statusbar = self.builder.get_object('statusbar') + self.model = self.builder.get_object('ca_list') + self.treeview = self.builder.get_object('treeview') + self.cell_renderer = gtk.CellRendererText() + + self.treeview.insert_column_with_attributes(-1, "Time", + self.cell_renderer, text=1).set_expand(True) + self.treeview.insert_column_with_attributes(-1, "Potential", + self.cell_renderer, text=0).set_expand(True) + + self.selection = self.treeview.get_selection() + self.selection.set_mode(gtk.SELECTION_MULTIPLE) + + def on_add_button_clicked(self, widget): + """Add current values in potential_entry and time_entry to model.""" + + self.statusbar.remove_all(0) + + try: + potential = int( + self.builder.get_object('potential_entry').get_text()) + time = int(self.builder.get_object('time_entry').get_text()) + + if (potential > 1499 or potential < -1500): + raise ValueError("Potential out of range") + if (time < 1 or time > 65535): + raise ValueError("Time out of range") + + self.model.append([potential, time]) + + except ValueError as err: + self.statusbar.push(0, str(err)) + except TypeError as err: + self.statusbar.push(0, str(err)) + + def on_remove_button_clicked(self, widget): + """Remove currently selected items from model.""" + # returns 2-tuple: treemodel, list of paths of selected rows + selected_rows = list(self.selection.get_selected_rows()[1]) + referencelist = [] + + for i in selected_rows: + referencelist.append(gtk.TreeRowReference(self.model, i)) + + for i in referencelist: + self.model.remove(self.model.get_iter(i.get_path())) + + def get_params(self): + """Returns a dict of parameters for experiment. Overrides superclass + method. + """ + parameters = {} + parameters['potential'] = [int(r[0]) for r in self.model] + parameters['time'] = [int(r[1]) for r in self.model] + + return parameters + +class LSV(ExpInterface): + """Experiment class for LSV.""" + def __init__(self): + """Adds entry listings to superclass's self.entry dict""" + super(LSV, self).__init__('interface/lsv.glade') + + self.entry['clean_mV'] = self.builder.get_object('clean_mV') + self.entry['clean_s'] = self.builder.get_object('clean_s') + self.entry['dep_mV'] = self.builder.get_object('dep_mV') + self.entry['dep_s'] = self.builder.get_object('dep_s') + self.entry['start'] = self.builder.get_object('start_entry') + self.entry['stop'] = self.builder.get_object('stop_entry') + self.entry['slope'] = self.builder.get_object('slope_entry') + +class CV(ExpInterface): + """Experiment class for CV.""" + def __init__(self): + """Adds entry listings to superclass's self.entry dict""" + super(CV, self).__init__('interface/cv.glade') + + self.entry['clean_mV'] = self.builder.get_object('clean_mV') + self.entry['clean_s'] = self.builder.get_object('clean_s') + self.entry['dep_mV'] = self.builder.get_object('dep_mV') + self.entry['dep_s'] = self.builder.get_object('dep_s') + self.entry['start'] = self.builder.get_object('start_entry') + self.entry['v1'] = self.builder.get_object('v1_entry') + self.entry['v2'] = self.builder.get_object('v2_entry') + self.entry['slope'] = self.builder.get_object('slope_entry') + self.entry['scans'] = self.builder.get_object('scans_entry') + +class SWV(ExpInterface): + """Experiment class for SWV.""" + def __init__(self): + """Adds entry listings to superclass's self.entry dict""" + super(SWV, self).__init__('interface/swv.glade') + + self.entry['clean_mV'] = self.builder.get_object('clean_mV') + self.entry['clean_s'] = self.builder.get_object('clean_s') + self.entry['dep_mV'] = self.builder.get_object('dep_mV') + self.entry['dep_s'] = self.builder.get_object('dep_s') + self.entry['start'] = self.builder.get_object('start_entry') + self.entry['stop'] = self.builder.get_object('stop_entry') + self.entry['step'] = self.builder.get_object('step_entry') + self.entry['pulse'] = self.builder.get_object('pulse_entry') + self.entry['freq'] = self.builder.get_object('freq_entry') + self.entry['scans'] = self.builder.get_object('scans_entry') + + def get_params(self): + """Extends superclass method to pass status of cyclic_checkbutton""" + parameters = {} + parameters['cyclic_checkbutton'] = self.builder.get_object( + 'cyclic_checkbutton').get_active() + parameters.update(super(SWV, self).get_params()) + + return parameters + +class DPV(ExpInterface): + """Experiment class for DPV.""" + def __init__(self): + """Adds entry listings to superclass's self.entry dict""" + super(DPV, self).__init__('interface/dpv.glade') + + self.entry['clean_mV'] = self.builder.get_object('clean_mV') + self.entry['clean_s'] = self.builder.get_object('clean_s') + self.entry['dep_mV'] = self.builder.get_object('dep_mV') + self.entry['dep_s'] = self.builder.get_object('dep_s') + self.entry['start'] = self.builder.get_object('start_entry') + self.entry['stop'] = self.builder.get_object('stop_entry') + self.entry['step'] = self.builder.get_object('step_entry') + self.entry['pulse'] = self.builder.get_object('pulse_entry') + self.entry['period'] = self.builder.get_object('period_entry') + self.entry['width'] = self.builder.get_object('width_entry') + +class ACV(ExpInterface): + """Experiment class for ACV.""" + def __init__(self): + """Adds entry listings to superclass's self.entry dict""" + super(ACV, self).__init__('interface/acv.glade') + + self.entry['start'] = self.builder.get_object('start_entry') + self.entry['stop'] = self.builder.get_object('stop_entry') + self.entry['slope'] = self.builder.get_object('slope_entry') + self.entry['amplitude'] = self.builder.get_object('amplitude_entry') + self.entry['freq'] = self.builder.get_object('freq_entry') + +class PD(ExpInterface): + """Experiment class for PD.""" + def __init__(self): + """Adds entry listings to superclass's self.entry dict""" + 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') \ 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 new file mode 100644 index 0000000000000000000000000000000000000000..6038229e29453b7e3352317ba3621d15c8ad21bd --- /dev/null +++ b/dstat-interface/dstat-interface/interface/exp_window.py @@ -0,0 +1,43 @@ +import interface.exp_int as exp + +class Experiments: + def __init__(self, builder): + self.builder = builder + + self.classes = {} + self.classes['cae'] = exp.Chronoamp() + self.classes['lsv'] = exp.LSV() + self.classes['cve'] = exp.CV() + self.classes['swv'] = exp.SWV() + self.classes['dpv'] = exp.DPV() + self.classes['acv'] = exp.ACV() + self.classes['pde'] = exp.PD() + + #fill exp_section + exp_section = self.builder.get_object('exp_section_box') + self.containers = {} + + for key, cls in self.classes.iteritems(): + self.containers[key] = cls.builder.get_object('scrolledwindow1') + + for key in self.containers: + self.containers[key].reparent(exp_section) + self.containers[key].hide() + + + def set_exp(self, selection): + """Changes parameter tab to selected experiment. Returns True if + successful, False if invalid selection received. + + Arguments: + selection -- id string of experiment type + """ + for key in self.containers: + self.containers[key].hide() + + self.containers[selection].show() + + return True + + def get_params(self, experiment): + return self.classes[experiment].get_params() \ No newline at end of file diff --git a/dstatInterface/interface/lsv.glade b/dstat-interface/dstat-interface/interface/lsv.glade similarity index 100% rename from dstatInterface/interface/lsv.glade rename to dstat-interface/dstat-interface/interface/lsv.glade diff --git a/dstatInterface/interface/pd.glade b/dstat-interface/dstat-interface/interface/pd.glade similarity index 100% rename from dstatInterface/interface/pd.glade rename to dstat-interface/dstat-interface/interface/pd.glade diff --git a/dstatInterface/interface/save.py b/dstat-interface/dstat-interface/interface/save.py similarity index 88% rename from dstatInterface/interface/save.py rename to dstat-interface/dstat-interface/interface/save.py index 4456d08f27347a589ab654ea16eac6603d02f031..8cc67f240fa7a235751abcb7e421650749db2b66 100644 --- a/dstatInterface/interface/save.py +++ b/dstat-interface/dstat-interface/interface/save.py @@ -7,7 +7,9 @@ from datetime import datetime def manSave(current_exp): exp = current_exp - fcd = gtk.FileChooserDialog("Save...", None, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) + fcd = gtk.FileChooserDialog("Save...", None, gtk.FILE_CHOOSER_ACTION_SAVE, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, gtk.RESPONSE_OK)) filters = [gtk.FileFilter()] filters[0].set_name("NumPy binary (.npy)") @@ -37,7 +39,10 @@ def manSave(current_exp): fcd.destroy() def plotSave(plot): - fcd = gtk.FileChooserDialog("Save Plot…", None, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) + fcd = gtk.FileChooserDialog("Save Plot…", None, + gtk.FILE_CHOOSER_ACTION_SAVE, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, gtk.RESPONSE_OK)) filters = [gtk.FileFilter()] filters[0].set_name("Portable Document Format (.pdf)") @@ -65,7 +70,7 @@ def plotSave(plot): if not path.endswith(".png"): path += ".png" - plot.figure.savefig(path) #savefig determines format from file extension + plot.figure.savefig(path) # determines format from file extension fcd.destroy() elif response == gtk.RESPONSE_CANCEL: diff --git a/dstatInterface/interface/swv.glade b/dstat-interface/dstat-interface/interface/swv.glade similarity index 100% rename from dstatInterface/interface/swv.glade rename to dstat-interface/dstat-interface/interface/swv.glade diff --git a/dstatInterface/interface_test.spec b/dstat-interface/dstat-interface/interface_test.spec.bak similarity index 100% rename from dstatInterface/interface_test.spec rename to dstat-interface/dstat-interface/interface_test.spec.bak diff --git a/dstatInterface/interface_test.py b/dstat-interface/dstat-interface/main.py similarity index 50% rename from dstatInterface/interface_test.py rename to dstat-interface/dstat-interface/main.py index abca328e397154c6b03f2ba4c5b572e8f2e954ec..e2cb161c97aeefb50fbce670b4ad5441c01fd0ff 100644 --- a/dstatInterface/interface_test.py +++ b/dstat-interface/dstat-interface/main.py @@ -1,59 +1,54 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +""" GUI Interface for Wheeler Lab DStat """ import sys try: import pygtk pygtk.require('2.0') -except: - pass +except ImportError: + print('PyGTK 2.0 not available') + sys.exit(1) try: import gtk - import gobject -except: +except ImportError: print('GTK not available') sys.exit(1) - try: import gobject -except: +except ImportError: print('gobject not available') sys.exit(1) -import interface.adc_pot as adc_pot -import interface.chronoamp as chronoamp -import interface.lsv as lsv -import interface.cv as cv -import interface.swv as swv -import interface.dpv as dpv -import interface.acv as acv -import interface.pd as pd import interface.save as save import dstat_comm as comm +import interface.exp_window as exp_window +import interface.adc_pot as adc_pot +import mpltest +import microdrop + from serial import SerialException import multiprocessing import time -import mpltest -import microdrop - class Error(Exception): + """Copies Exception class""" pass class InputError(Error): - """Exception raised for errors in the input. + """Exception raised for errors in the input. Extends Error class. - Attributes: + Attributes: expr -- input expression in which the error occurred msg -- error message - """ + """ def __init__(self, expr, msg): self.expr = expr self.msg = msg -class main: - +class Main(object): + """Main program """ def __init__(self): self.builder = gtk.Builder() self.builder.add_from_file('interface/dstatinterface.glade') @@ -69,19 +64,14 @@ class main: self.stopbutton = self.builder.get_object('pot_stop') self.startbutton = self.builder.get_object('pot_start') self.adc_pot = adc_pot.adc_pot() - self.chronoamp = chronoamp.chronoamp() - self.lsv = lsv.lsv() - self.cv = cv.cv() - self.swv = swv.swv() - self.dpv = dpv.dpv() - self.acv = acv.acv() - self.pd = pd.pd() self.error_context_id = self.statusbar.get_context_id("error") self.message_context_id = self.statusbar.get_context_id("message") self.plotwindow = self.builder.get_object('plotbox') + self.exp_window = exp_window.Experiments(self.builder) + #setup autosave self.autosave_checkbox = self.builder.get_object('autosave_checkbutton') self.autosavedir_button = self.builder.get_object('autosavedir_button') @@ -89,23 +79,6 @@ class main: self.plot = mpltest.plotbox(self.plotwindow) - #fill exp_section - self.exp_section = self.builder.get_object('exp_section_box') - self.chronoamp_container = self.chronoamp.builder.get_object('scrolledwindow1') - self.chronoamp_container.reparent(self.exp_section) - self.lsv_container = self.lsv.builder.get_object('scrolledwindow1') - self.lsv_container.reparent(self.exp_section) - self.cv_container = self.cv.builder.get_object('scrolledwindow1') - self.cv_container.reparent(self.exp_section) - self.swv_container = self.swv.builder.get_object('scrolledwindow1') - self.swv_container.reparent(self.exp_section) - self.dpv_container = self.dpv.builder.get_object('scrolledwindow1') - self.dpv_container.reparent(self.exp_section) - self.acv_container = self.acv.builder.get_object('scrolledwindow1') - self.acv_container.reparent(self.exp_section) - self.pd_container = self.pd.builder.get_object('scrolledwindow1') - self.pd_container.reparent(self.exp_section) - #fill adc_pot_box self.adc_pot_box = self.builder.get_object('gain_adc_box') self.adc_pot_container = self.adc_pot.builder.get_object('vbox1') @@ -127,7 +100,7 @@ class main: #initialize experiment selection combobox self.expcombobox = self.builder.get_object('expcombobox') self.expcombobox.pack_start(self.cell, True) - self.expcombobox.add_attribute(self.cell, 'text', 1) + self.expcombobox.add_attribute(self.cell, 'text', 2) self.expcombobox.set_active(0) self.spinner = self.builder.get_object('spinner') @@ -136,67 +109,44 @@ class main: self.mainwindow.set_title("Dstat Interface 0.1") self.mainwindow.show_all() - ##hide unused experiment controls - #self.chronoamp_container.hide() - self.lsv_container.hide() - self.cv_container.hide() - self.swv_container.hide() - self.dpv_container.hide() - self.acv_container.hide() - self.pd_container.hide() + self.on_expcombobox_changed() self.expnumber = 0 - self.menu_dropbot_connect = self.builder.get_object('menu_dropbot_connect') - self.menu_dropbot_disconnect = self.builder.get_object('menu_dropbot_disconnect') + self.menu_dropbot_connect = self.builder.get_object( + 'menu_dropbot_connect') + self.menu_dropbot_disconnect = self.builder.get_object( + 'menu_dropbot_disconnect') self.dropbot_enabled = False self.dropbot_triggered = False - def exp_param_show(self, selection): - self.chronoamp_container.hide() - self.lsv_container.hide() - self.cv_container.hide() - self.swv_container.hide() - self.dpv_container.hide() - self.acv_container.hide() - self.pd_container.hide() - - self.statusbar.remove_all(self.error_context_id) - - if selection == 0: - self.chronoamp_container.show() - elif selection == 1: - self.lsv_container.show() - elif selection == 2: - self.cv_container.show() - elif selection == 3: - self.swv_container.show() - elif selection == 4: - self.dpv_container.show() - elif selection == 5: - self.acv_container.show() - elif selection == 6: - self.pd_container.show() - else: - self.statusbar.push(self.error_context_id, "Experiment not yet implemented") - def on_window1_destroy(self, object, data=None): + """ Quit when main window closed.""" print "quit with cancel" gtk.main_quit() def on_gtk_quit_activate(self, menuitem, data=None): + """Quit when Quit selected from menu.""" print "quit from menu" gtk.main_quit() def on_gtk_about_activate(self, menuitem, data=None): + """Display the about window.""" print "help about selected" - self.response = self.aboutdialog.run() #waits for user to click close + self.response = self.aboutdialog.run() # waits for user to click close self.aboutdialog.hide() def on_expcombobox_changed(self, data=None): - self.exp_param_show(self.expcombobox.get_active()) + """Change the experiment window when experiment box changed.""" + model = self.expcombobox.get_model() + _, id, _ = model[self.expcombobox.get_active()] # id is in 2nd col + self.statusbar.remove_all(self.error_context_id) + if not self.exp_window.set_exp(id): + self.statusbar.push( + self.error_context_id, "Experiment not yet implemented") def on_serial_refresh_clicked(self, data=None): + """Refresh list of serial devices.""" self.serial_devices.refresh() self.serial_liststore.clear() @@ -204,21 +154,22 @@ class main: self.serial_liststore.append([i]) def on_pot_start_clicked(self, data=None): - - def exceptions(): #cleans up after errors + """Run currently visible experiment.""" + def exceptions(): + """ Cleans up after errors """ if self.dropbot_enabled == True: if self.dropbot_triggered == True: self.dropbot_triggered = False print "finallydone" self.microdrop.reply(microdrop.EXPFINISHED) - self.microdrop_proc = gobject.timeout_add(500, self.microdrop_listen) + self.microdrop_proc = gobject.timeout_add(500, + self.microdrop_listen) self.spinner.stop() self.startbutton.set_sensitive(True) self.stopbutton.set_sensitive(False) selection = self.expcombobox.get_active() parameters = {} - view_parameters = {} if self.adc_pot.buffer_toggle.get_active(): #True if box checked parameters['adc_buffer'] = "2" @@ -229,9 +180,12 @@ class main: pga_model = self.adc_pot.pga_combobox.get_model() gain_model = self.adc_pot.gain_combobox.get_model() - parameters['adc_rate'] = srate_model.get_value(self.adc_pot.srate_combobox.get_active_iter(), 2) #third column - parameters['adc_pga'] = pga_model.get_value(self.adc_pot.pga_combobox.get_active_iter(), 2) - parameters['gain'] = gain_model.get_value(self.adc_pot.gain_combobox.get_active_iter(), 2) + parameters['adc_rate'] = srate_model.get_value( + self.adc_pot.srate_combobox.get_active_iter(), 2) # third column + parameters['adc_pga'] = pga_model.get_value( + self.adc_pot.pga_combobox.get_active_iter(), 2) + parameters['gain'] = gain_model.get_value( + self.adc_pot.gain_combobox.get_active_iter(), 2) self.line = 0 self.lastline = 0 @@ -243,12 +197,12 @@ class main: self.statusbar.remove_all(self.error_context_id) try: - if selection == 0: #CA - parameters['potential'] = [int(r[0]) for r in self.chronoamp.model] - parameters['time'] = [int(r[1]) for r in self.chronoamp.model] - + if selection == 0: # CA + # Add experiment parameters to existing + parameters.update(self.exp_window.get_params('cae')) if not parameters['potential']: - raise InputError(parameters['potential'],"Step table is empty") + raise InputError(parameters['potential'], + "Step table is empty") self.recv_p, self.send_p = multiprocessing.Pipe(duplex=True) self.current_exp = comm.chronoamp(parameters, self.send_p) @@ -261,40 +215,46 @@ class main: for i in self.current_exp.commands: self.rawbuffer.insert_at_cursor(i) - self.current_exp.run_wrapper(self.serial_liststore.get_value(self.serial_combobox.get_active_iter(), 0)) - - self.send_p.close() #need to close this copy of connection object for EOF signal to work + 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.plot_proc = gobject.timeout_add(200, self.experiment_running_plot) + self.plot_proc = gobject.timeout_add(200, + self.experiment_running_plot) gobject.idle_add(self.experiment_running) return - elif selection == 1: #LSV - parameters['clean_mV'] = int(self.lsv.clean_mV.get_text()) - parameters['clean_s'] = int(self.lsv.clean_s.get_text()) - parameters['dep_mV'] = int(self.lsv.dep_mV.get_text()) - parameters['dep_s'] = int(self.lsv.dep_s.get_text()) - parameters['start'] = int(self.lsv.start_entry.get_text()) - parameters['stop'] = int(self.lsv.stop_entry.get_text()) - parameters['slope'] = int(self.lsv.slope_entry.get_text()) + elif selection == 1: # LSV + parameters.update(self.exp_window.get_params('lsv')) #check parameters are within hardware limits - if (parameters['clean_mV'] > 1499 or parameters['clean_mV'] < -1500): - raise InputError(parameters['clean_mV'],"Clean potential exceeds hardware limits.") - if (parameters['dep_mV'] > 1499 or parameters['dep_mV'] < -1500): - raise InputError(parameters['dep_mV'],"Deposition potential exceeds hardware limits.") + if (parameters['clean_mV'] > 1499 or + parameters['clean_mV'] < -1500): + raise InputError(parameters['clean_mV'], + "Clean potential exceeds hardware limits.") + if (parameters['dep_mV'] > 1499 or + parameters['dep_mV'] < -1500): + raise InputError(parameters['dep_mV'], + "Deposition potential exceeds hardware limits.") if (parameters['clean_s'] < 0): - raise InputError(parameters['clean_s'],"Clean time cannot be negative.") + raise InputError(parameters['clean_s'], + "Clean time cannot be negative.") if (parameters['dep_s'] < 0): - raise InputError(parameters['dep_s'],"Deposition time cannot be negative.") + raise InputError(parameters['dep_s'], + "Deposition time cannot be negative.") if (parameters['start'] > 1499 or parameters['start'] < -1500): - raise InputError(parameters['start'],"Start parameter exceeds hardware limits.") + raise InputError(parameters['start'], + "Start parameter exceeds hardware limits.") if (parameters['stop'] > 1499 or parameters['stop'] < -1500): - raise InputError(parameters['stop'],"Stop parameter exceeds hardware limits.") + raise InputError(parameters['stop'], + "Stop parameter exceeds hardware limits.") if (parameters['slope'] > 2000 or parameters['slope'] < 1): - raise InputError(parameters['slope'],"Slope parameter exceeds hardware limits.") + raise InputError(parameters['slope'], + "Slope parameter exceeds hardware limits.") if parameters['start'] == parameters['stop']: - raise InputError(parameters['start'],"Start cannot equal Stop.") + raise InputError(parameters['start'], + "Start cannot equal Stop.") self.recv_p, self.send_p = multiprocessing.Pipe(duplex=True) self.current_exp = comm.lsv_exp(parameters, self.send_p) @@ -307,46 +267,53 @@ class main: for i in self.current_exp.commands: self.rawbuffer.insert_at_cursor(i) - self.current_exp.run_wrapper(self.serial_liststore.get_value(self.serial_combobox.get_active_iter(), 0)) + 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) + self.plot_proc = gobject.timeout_add(200, + self.experiment_running_plot) gobject.idle_add(self.experiment_running) return - elif selection == 2: #CV - parameters['clean_mV'] = int(self.cv.clean_mV.get_text()) - parameters['clean_s'] = int(self.cv.clean_s.get_text()) - parameters['dep_mV'] = int(self.cv.dep_mV.get_text()) - parameters['dep_s'] = int(self.cv.dep_s.get_text()) - parameters['start'] = int(self.cv.start_entry.get_text()) - parameters['slope'] = int(self.cv.slope_entry.get_text()) - parameters['v1'] = int(self.cv.v1_entry.get_text()) - parameters['v2'] = int(self.cv.v2_entry.get_text()) - parameters['scans'] = int(self.cv.scans_entry.get_text()) + elif selection == 2: # CV + parameters.update(self.exp_window.get_params('cve')) - #check parameters are within hardware limits - if (parameters['clean_mV'] > 1499 or parameters['clean_mV'] < -1500): - raise InputError(parameters['clean_mV'],"Clean potential exceeds hardware limits.") - if (parameters['dep_mV'] > 1499 or parameters['dep_mV'] < -1500): - raise InputError(parameters['dep_mV'],"Deposition potential exceeds hardware limits.") + # check parameters are within hardware limits + if (parameters['clean_mV'] > 1499 or + parameters['clean_mV'] < -1500): + raise InputError(parameters['clean_mV'], + "Clean potential exceeds hardware limits.") + if (parameters['dep_mV'] > 1499 or + parameters['dep_mV'] < -1500): + raise InputError(parameters['dep_mV'], + "Deposition potential exceeds hardware limits.") if (parameters['clean_s'] < 0): - raise InputError(parameters['clean_s'],"Clean time cannot be negative.") + raise InputError(parameters['clean_s'], + "Clean time cannot be negative.") if (parameters['dep_s'] < 0): - raise InputError(parameters['dep_s'],"Deposition time cannot be negative.") + raise InputError(parameters['dep_s'], + "Deposition time cannot be negative.") if (parameters['start'] > 1499 or parameters['start'] < -1500): - raise InputError(parameters['start'],"Start parameter exceeds hardware limits.") + raise InputError(parameters['start'], + "Start parameter exceeds hardware limits.") if (parameters['slope'] > 2000 or parameters['slope'] < 1): - raise InputError(parameters['slope'],"Slope parameter exceeds hardware limits.") + raise InputError(parameters['slope'], + "Slope parameter exceeds hardware limits.") if (parameters['v1'] > 1499 or parameters['v1'] < -1500): - raise InputError(parameters['v1'],"Vertex 1 parameter exceeds hardware limits.") + raise InputError(parameters['v1'], + "Vertex 1 parameter exceeds hardware limits.") if (parameters['v2'] > 1499 or parameters['v2'] < -1500): - raise InputError(parameters['v2'],"Vertex 2 parameter exceeds hardware limits.") + raise InputError(parameters['v2'], + "Vertex 2 parameter exceeds hardware limits.") if (parameters['scans'] < 1 or parameters['scans'] > 255): - raise InputError(parameters['scans'], "Scans parameter outside limits.") + raise InputError(parameters['scans'], + "Scans parameter outside limits.") if parameters['v1'] == parameters['v2']: - raise InputError(parameters['v1'],"Vertex 1 cannot equal Vertex 2.") + raise InputError(parameters['v1'], + "Vertex 1 cannot equal Vertex 2.") self.recv_p, self.send_p = multiprocessing.Pipe(duplex=True) self.current_exp = comm.cv_exp(parameters, self.send_p) @@ -359,53 +326,62 @@ class main: for i in self.current_exp.commands: self.rawbuffer.insert_at_cursor(i) - self.current_exp.run_wrapper(self.serial_liststore.get_value(self.serial_combobox.get_active_iter(), 0)) + 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) + self.plot_proc = gobject.timeout_add(200, + self.experiment_running_plot) gobject.idle_add(self.experiment_running) return - elif selection == 3: #SWV - parameters['clean_mV'] = int(self.swv.clean_mV.get_text()) - parameters['clean_s'] = int(self.swv.clean_s.get_text()) - parameters['dep_mV'] = int(self.swv.dep_mV.get_text()) - parameters['dep_s'] = int(self.swv.dep_s.get_text()) - parameters['start'] = int(self.swv.start_entry.get_text()) - parameters['stop'] = int(self.swv.stop_entry.get_text()) - parameters['step'] = int(self.swv.step_entry.get_text()) - parameters['pulse'] = int(self.swv.pulse_entry.get_text()) - parameters['freq'] = int(self.swv.freq_entry.get_text()) + elif selection == 3: # SWV + parameters.update(self.exp_window.get_params('swv')) - if self.swv.cyclic_checkbutton.get_active(): - parameters['scans'] = int(self.swv.scans_entry.get_text()) + if parameters['cyclic_checkbutton'] : if parameters['scans'] < 1: - raise InputError(parameters['scans'],"Must have at least one scan.") + raise InputError(parameters['scans'], + "Must have at least one scan.") else: parameters['scans'] = 0 - #check parameters are within hardware limits (doesn't check if pulse will go out of bounds, but instrument checks this (I think)) - if (parameters['clean_mV'] > 1499 or parameters['clean_mV'] < -1500): - raise InputError(parameters['clean_mV'],"Clean potential exceeds hardware limits.") - if (parameters['dep_mV'] > 1499 or parameters['dep_mV'] < -1500): - raise InputError(parameters['dep_mV'],"Deposition potential exceeds hardware limits.") + # check parameters are within hardware limits (doesn't + # check if pulse will go out of bounds, but instrument + # checks this (I think)) + if (parameters['clean_mV'] > 1499 or + parameters['clean_mV'] < -1500): + raise InputError(parameters['clean_mV'], + "Clean potential exceeds hardware limits.") + if (parameters['dep_mV'] > 1499 or + parameters['dep_mV'] < -1500): + raise InputError(parameters['dep_mV'], + "Deposition potential exceeds hardware limits.") if (parameters['clean_s'] < 0): - raise InputError(parameters['clean_s'],"Clean time cannot be negative.") + raise InputError(parameters['clean_s'], + "Clean time cannot be negative.") if (parameters['dep_s'] < 0): - raise InputError(parameters['dep_s'],"Deposition time cannot be negative.") + raise InputError(parameters['dep_s'], + "Deposition time cannot be negative.") if (parameters['start'] > 1499 or parameters['start'] < -1500): - raise InputError(parameters['start'],"Start parameter exceeds hardware limits.") + raise InputError(parameters['start'], + "Start parameter exceeds hardware limits.") if (parameters['step'] > 200 or parameters['step'] < 1): - raise InputError(parameters['step'],"Step height parameter exceeds hardware limits.") + raise InputError(parameters['step'], + "Step height parameter exceeds hardware limits.") if (parameters['stop'] > 1499 or parameters['stop'] < -1500): - raise InputError(parameters['stop'],"Stop parameter exceeds hardware limits.") + raise InputError(parameters['stop'], + "Stop parameter exceeds hardware limits.") if (parameters['pulse'] > 150 or parameters['pulse'] < 1): - raise InputError(parameters['pulse'],"Pulse height parameter exceeds hardware limits.") + raise InputError(parameters['pulse'], + "Pulse height parameter exceeds hardware limits.") if (parameters['freq'] < 1 or parameters['freq'] > 1000): - raise InputError(parameters['freq'], "Frequency parameter outside limits.") + raise InputError(parameters['freq'], + "Frequency parameter outside limits.") if parameters['start'] == parameters['stop']: - raise InputError(parameters['start'],"Start cannot equal Stop.") + raise InputError(parameters['start'], + "Start cannot equal Stop.") self.recv_p, self.send_p = multiprocessing.Pipe(duplex=True) self.current_exp = comm.swv_exp(parameters, self.send_p) @@ -418,51 +394,58 @@ class main: for i in self.current_exp.commands: self.rawbuffer.insert_at_cursor(i) - self.current_exp.run_wrapper(self.serial_liststore.get_value(self.serial_combobox.get_active_iter(), 0)) + 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) + self.plot_proc = gobject.timeout_add(200, + self.experiment_running_plot) gobject.idle_add(self.experiment_running) return - elif selection == 4: #DPV - parameters['clean_mV'] = int(self.dpv.clean_mV.get_text()) - parameters['clean_s'] = int(self.dpv.clean_s.get_text()) - parameters['dep_mV'] = int(self.dpv.dep_mV.get_text()) - parameters['dep_s'] = int(self.dpv.dep_s.get_text()) - parameters['start'] = int(self.dpv.start_entry.get_text()) - parameters['stop'] = int(self.dpv.stop_entry.get_text()) - parameters['step'] = int(self.dpv.step_entry.get_text()) - parameters['pulse'] = int(self.dpv.pulse_entry.get_text()) - parameters['period'] = int(self.dpv.period_entry.get_text()) - parameters['width'] = int(self.dpv.width_entry.get_text()) + elif selection == 4: # DPV + parameters.update(self.exp_window.get_params('dpv')) - #check parameters are within hardware limits (doesn't check if pulse will go out of bounds, but instrument checks this (I think)) - if (parameters['clean_mV'] > 1499 or parameters['clean_mV'] < -1500): - raise InputError(parameters['clean_mV'],"Clean potential exceeds hardware limits.") - if (parameters['dep_mV'] > 1499 or parameters['dep_mV'] < -1500): - raise InputError(parameters['dep_mV'],"Deposition potential exceeds hardware limits.") + if (parameters['clean_mV'] > 1499 or + parameters['clean_mV'] < -1500): + raise InputError(parameters['clean_mV'], + "Clean potential exceeds hardware limits.") + if (parameters['dep_mV'] > 1499 or + parameters['dep_mV'] < -1500): + raise InputError(parameters['dep_mV'], + "Deposition potential exceeds hardware limits.") if (parameters['clean_s'] < 0): - raise InputError(parameters['clean_s'],"Clean time cannot be negative.") + raise InputError(parameters['clean_s'], + "Clean time cannot be negative.") if (parameters['dep_s'] < 0): - raise InputError(parameters['dep_s'],"Deposition time cannot be negative.") + raise InputError(parameters['dep_s'], + "Deposition time cannot be negative.") if (parameters['start'] > 1499 or parameters['start'] < -1500): - raise InputError(parameters['start'],"Start parameter exceeds hardware limits.") + raise InputError(parameters['start'], + "Start parameter exceeds hardware limits.") if (parameters['step'] > 200 or parameters['step'] < 1): - raise InputError(parameters['step'],"Step height parameter exceeds hardware limits.") + raise InputError(parameters['step'], + "Step height parameter exceeds hardware limits.") if (parameters['stop'] > 1499 or parameters['stop'] < -1500): - raise InputError(parameters['stop'],"Stop parameter exceeds hardware limits.") + raise InputError(parameters['stop'], + "Stop parameter exceeds hardware limits.") if (parameters['pulse'] > 150 or parameters['pulse'] < 1): - raise InputError(parameters['pulse'],"Pulse height parameter exceeds hardware limits.") + raise InputError(parameters['pulse'], + "Pulse height parameter exceeds hardware limits.") if (parameters['period'] < 1 or parameters['period'] > 1000): - raise InputError(parameters['period'], "Period parameter outside limits.") + raise InputError(parameters['period'], + "Period parameter outside limits.") if (parameters['width'] < 1 or parameters['width'] > 1000): - raise InputError(parameters['width'], "Width parameter outside limits.") + raise InputError(parameters['width'], + "Width parameter outside limits.") if parameters['period'] <= parameters['width']: - raise InputError(parameters['width'],"Width must be less than period.") + raise InputError(parameters['width'], + "Width must be less than period.") if parameters['start'] == parameters['stop']: - raise InputError(parameters['start'],"Start cannot equal Stop.") + raise InputError(parameters['start'], + "Start cannot equal Stop.") self.recv_p, self.send_p = multiprocessing.Pipe(duplex=True) self.current_exp = comm.dpv_exp(parameters, self.send_p) @@ -475,47 +458,62 @@ class main: for i in self.current_exp.commands: self.rawbuffer.insert_at_cursor(i) - self.current_exp.run_wrapper(self.serial_liststore.get_value(self.serial_combobox.get_active_iter(), 0)) + 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) + 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.") + self.statusbar.push(self.error_context_id, + "Experiment not yet implemented.") exceptions() except ValueError: - self.statusbar.push(self.error_context_id, "Experiment parameters must be integers.") + self.statusbar.push(self.error_context_id, + "Experiment parameters must be integers.") exceptions() - except InputError as e: - self.statusbar.push(self.error_context_id, e.msg) + except InputError as err: + self.statusbar.push(self.error_context_id, err.msg) exceptions() except SerialException: - self.statusbar.push(self.error_context_id, "Could not establish serial connection.") + self.statusbar.push(self.error_context_id, + "Could not establish serial connection.") exceptions() - except AssertionError as e: - self.statusbar.push(self.error_context_id, str(e)) + except AssertionError as err: + self.statusbar.push(self.error_context_id, str(err)) exceptions() def experiment_running(self): + """Receive data from experiment process and add to current_exp.data. + Run in GTK main loop. + + 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(): self.line, data = self.recv_p.recv() if self.line > self.lastdataline: - self.current_exp.data += [[],[]] + self.current_exp.data += [[], []] if len(data) > 2: - self.current_exp.data_extra += [[],[]] + self.current_exp.data_extra += [[], []] self.lastdataline = self.line for i in range(2): self.current_exp.data[2*self.line+i].append(data[i]) - if len(data) > 2: - self.current_exp.data_extra[2*self.line+i].append(data[i]) + if len(data) > 2: + self.current_exp.data_extra[2*self.line+i].append( + data[i+2]) else: time.sleep(.001) return True @@ -527,23 +525,31 @@ class main: 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 + removed from GTK's queue. + """ if self.line > self.lastline: self.plot.addline() - self.plot.updateline(self.current_exp, self.lastline) #make sure all of last line is added + # make sure all of last line is added + self.plot.updateline(self.current_exp, self.lastline) self.lastline = self.line self.plot.updateline(self.current_exp, self.line) self.plot.redraw() return True def experiment_done(self): - gobject.source_remove(self.plot_proc) #stop automatic plot update - self.experiment_running_plot() #make sure all data updated on plot + """Clean up after data acquisition is complete. Update plot and + copy data to raw data tab. Saves data if autosave enabled. + """ + gobject.source_remove(self.plot_proc) # stop automatic plot update + self.experiment_running_plot() # make sure all data updated on plot self.databuffer.set_text("") self.databuffer.place_cursor(self.databuffer.get_start_iter()) self.rawbuffer.insert_at_cursor("\n") -# self.rawbuffer.set_text("") -# self.rawbuffer.place_cursor(self.rawbuffer.get_start_iter()) + self.rawbuffer.set_text("") + self.rawbuffer.place_cursor(self.rawbuffer.get_start_iter()) for col in zip(*self.current_exp.data): for row in col: @@ -557,8 +563,10 @@ class main: self.databuffer.insert_at_cursor("\n") if self.autosave_checkbox.get_active(): - save.autoSave(self.current_exp, self.autosavedir_button, self.autosavename.get_text(), self.expnumber) - save.autoPlot(self.plot, self.autosavedir_button, self.autosavename.get_text(), self.expnumber) + save.autoSave(self.current_exp, self.autosavedir_button, + self.autosavename.get_text(), self.expnumber) + save.autoPlot(self.plot, self.autosavedir_button, + self.autosavename.get_text(), self.expnumber) self.expnumber += 1 if self.dropbot_enabled == True: @@ -566,33 +574,40 @@ class main: self.dropbot_triggered = False print "expdone" self.microdrop.reply(microdrop.EXPFINISHED) - self.microdrop_proc = gobject.timeout_add(500, self.microdrop_listen) + self.microdrop_proc = gobject.timeout_add(500, + self.microdrop_listen) self.spinner.stop() self.startbutton.set_sensitive(True) self.stopbutton.set_sensitive(False) def on_pot_stop_clicked(self, data=None): + """Stop current experiment. Signals experiment process to stop.""" if self.recv_p: print "stop" self.recv_p.send('a') def on_file_save_exp_activate(self, menuitem, data=None): + """Activate dialogue to save current experiment data. """ if self.current_exp: save_inst = save.manSave(self.current_exp) def on_file_save_plot_activate(self, menuitem, data=None): + """Activate dialogue to save current plot.""" save_inst = save.plotSave(self.plot) def on_menu_dropbot_connect_activate(self, menuitem, data=None): + """Listen for remote control connection from µDrop.""" self.microdrop = microdrop.microdropConnection() self.dropbot_enabled = True self.menu_dropbot_connect.set_sensitive(False) self.menu_dropbot_disconnect.set_sensitive(True) - self.statusbar.push(self.message_context_id, "Waiting for µDrop to connect…") + self.statusbar.push(self.message_context_id, + "Waiting for µDrop to connect…") self.microdrop_proc = gobject.timeout_add(500, self.microdrop_listen) def on_menu_dropbot_disconnect_activate(self, menuitem, data= None): + """Disconnect µDrop connection and stop listening.""" gobject.source_remove(self.microdrop_proc) self.microdrop.reset() del self.microdrop @@ -601,32 +616,35 @@ class main: self.menu_dropbot_disconnect.set_sensitive(False) def microdrop_listen(self): + """Manage signals from µDrop. Must be added to GTK's main loop to + run periodically. + """ drdy, data = self.microdrop.listen() if drdy == False: return True - if self.microdrop.connected == False: - if data == microdrop.CONREQ: - print "INFO: µDrop connected" - self.statusbar.push(self.message_context_id, "µDrop connected.") - self.microdrop.reply(microdrop.CONREP) - self.microdrop.connected = True + + if data == microdrop.EXP_FINISH_REQ: + if self.dropbot_triggered: + self.on_pot_start_clicked() + return False # Removes function from GTK's main loop else: - print "WAR: Invalid µDrop connection request." - self.microdrop.reply(microdrop.INVAL_CMD) + print "WAR: µDrop requested experiment finish confirmation \ + without starting experiment." + self.microdrop.reply(microdrop.EXPFINISHED) + elif data == microdrop.STARTEXP: + self.microdrop.connected = True + self.statusbar.push(self.message_context_id, "µDrop connected.") self.dropbot_triggered = True - self.on_pot_start_clicked() - return False + self.microdrop.reply(microdrop.START_REP) else: print "WAR: Received invalid command from µDrop" self.microdrop.reply(microdrop.INVAL_CMD) return True - - if __name__ == "__main__": multiprocessing.freeze_support() gobject.threads_init() - main = main() + MAIN = Main() gtk.main() \ No newline at end of file diff --git a/dstatInterface/microdrop.py b/dstat-interface/dstat-interface/microdrop.py similarity index 52% rename from dstatInterface/microdrop.py rename to dstat-interface/dstat-interface/microdrop.py index 95ea34cbe86d9998730c12a9eb91151e23782028..8546ad38a13fccc093f49b27f2ef290979e958f3 100644 --- a/dstatInterface/microdrop.py +++ b/dstat-interface/dstat-interface/microdrop.py @@ -4,26 +4,39 @@ import zmq.error #signals CONREQ = "0" CONREP = "1" -STARTEXP = "10" -EXPFINISHED = "11" +STARTEXP = "start" +START_REP = "started" +EXP_FINISH_REQ = "notify_completion" +EXPFINISHED = "completed" INVAL_CMD = "99" #States RECV = 0 SEND = 1 -class microdropConnection: - +class microdropConnection(object): + """Manages microdrop connection over TCP with zmq""" def __init__(self, port=6789): + """Create zmq context and bind to port. Should be called manually + to reinitialize if reset is called. + + Keyword arguments: + port -- the TCP to bind to on localhost + """ self.port = port self.connected = False self.state = RECV self.ctx = zmq.Context() self.soc = zmq.Socket(self.ctx, zmq.REP) - self.soc.bind("".join(['tcp://*:',str(self.port)])) + self.soc.bind("".join(['tcp://*:', str(self.port)])) def listen(self): + """Perform non-blocking recv on zmq port. self.state must be RECV. + Returns a tuple: + [0] -- True if a message was received, False otherwise. + [1] -- The recieved message or "" if no message received. + """ if self.state == SEND: print "WAR: Microdrop Connection state invalid, resetting..." self.reset() @@ -36,6 +49,11 @@ class microdropConnection: return (False, "") def reply(self, data): + """Sends a reply on zmq port. self.state must be SEND. + + Arguments: + data -- a str to be sent + """ if self.state == RECV: print "WAR: Microdrop Connection state invalid, resetting..." self.reset() @@ -46,6 +64,7 @@ class microdropConnection: return True def reset(self): - self.soc.unbind("".join(['tcp://*:',str(self.port)])) + """Reset zmq interface. Must call __init__ again to reinitialize.""" + self.soc.unbind("".join(['tcp://*:', str(self.port)])) del self.soc del self.ctx diff --git a/dstat-interface/dstat-interface/mpltest.py b/dstat-interface/dstat-interface/mpltest.py new file mode 100644 index 0000000000000000000000000000000000000000..59e53922f4271278cf56079b117d205398cc53f9 --- /dev/null +++ b/dstat-interface/dstat-interface/mpltest.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +""" +Creates data plot. +""" +import gtk +from matplotlib.figure import Figure + +#from matplotlib.backends.backend_gtkcairo\ +# import FigureCanvasGTKCairo as FigureCanvas +#from matplotlib.backends.backend_gtkcairo\ +# import NavigationToolbar2Cairo as NavigationToolbar +from matplotlib.backends.backend_gtkagg \ + import FigureCanvasGTKAgg as FigureCanvas +from matplotlib.backends.backend_gtkagg \ + import NavigationToolbar2GTKAgg as NavigationToolbar + +class plotbox(object): + """Contains main data plot and associated methods.""" + def __init__(self, plotwindow_instance): + """Creates plot and moves it to a gtk container. + + Arguments: + plotwindow_instance -- gtk container to hold plot. + """ + + self.figure = Figure() + self.figure.subplots_adjust(left=0.07, bottom=0.07, + right=0.96, top=0.96) + self.axe1 = self.figure.add_subplot(111) + + self.lines = self.axe1.plot([0, 1], [0, 1]) + + self.axe1.ticklabel_format(style='sci', scilimits=(0, 3), + useOffset=False, axis='y') + + self.canvas = FigureCanvas(self.figure) + self.win = gtk.Window() + self.vbox = gtk.VBox() + self.win.add(self.vbox) + self.vbox.pack_start(self.canvas) + self.toolbar = NavigationToolbar(self.canvas, self.win) + self.vbox.pack_start(self.toolbar, False, False) + self.vbox.reparent(plotwindow_instance) + + def clearall(self): + """Remove all lines on plot. """ + for i in self.lines: + i.remove() + self.lines = self.axe1.plot([0, 1], [0, 1]) + + def clearline(self, line_number): + """Remove a line specified by line_number.""" + self.lines[line_number].remove() + self.lines.pop(line_number) + + def addline(self): + """Add a new line to plot. (initialized with dummy data)))""" + self.lines.append(self.axe1.plot([0, 1], [0, 1])[0]) + + def updateline(self, Experiment, line_number): + """Update a line specified by line_number with data stored in + the Experiment instance. + """ + # limits display to 2000 data points per line + divisor = len(Experiment.data[1+line_number*2]) // 2000 + 1 + + self.lines[line_number].set_ydata( + Experiment.data[1+line_number*2][1::divisor]) + self.lines[line_number].set_xdata( + Experiment.data[line_number*2][1::divisor]) + + def changetype(self, Experiment): + """Change plot type. Set axis labels and x bounds to those stored + in the Experiment instance. + """ + self.axe1.set_xlabel(Experiment.xlabel) + self.axe1.set_ylabel(Experiment.ylabel) + self.axe1.set_xlim(Experiment.xmin, Experiment.xmax) + + self.figure.canvas.draw() + + def redraw(self): + """Autoscale and refresh the plot.""" + self.axe1.relim() + self.axe1.autoscale(True, axis = 'y') + self.figure.canvas.draw() + + return True + diff --git a/dstatInterface/setup.py b/dstat-interface/dstat-interface/setup.py similarity index 100% rename from dstatInterface/setup.py rename to dstat-interface/dstat-interface/setup.py diff --git a/dstat-interface/setup.py b/dstat-interface/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/dstatInterface/glade1.py b/dstatInterface/glade1.py deleted file mode 100644 index 454320aa4e21bda38289abc438a88655457d1c99..0000000000000000000000000000000000000000 --- a/dstatInterface/glade1.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python - -import sys -try: - import pygtk - pygtk.require('2.0') -except: - pass -try: - import gtk -except: - print('GTK not available') - sys.exit(1) -try: - import math -except: - print('math lib missing') - sys.exit(1) - -# we can call it just about anything we want -class Buglump: - - # This first define is for our on_window1_destroy signal we created in the - # Glade designer. The print message does just that and prints to the terminal - # which can be useful for debugging. The 'object' if you remember is the signal - # class we picked from GtkObject. - def on_window1_destroy(self, object, data=None): - print "quit with cancel" - gtk.main_quit() - - # This is the same as above but for our menu item. - def on_gtk_quit_activate(self, menuitem, data=None): - print "quit from menu" - gtk.main_quit() - - def on_gtk_about_activate(self, menuitem, data=None): - print "help about selected" - self.response = self.aboutdialog.run() #waits for user to click close - could test response with if - self.aboutdialog.hide() - - def on_push_status_activate(self, menuitem, data=None): #adds message to top of stack - self.status_count += 1 #increment status_count - self.statusbar.push(self.context_id, "Message number %s" % str(self.status_count)) - - def on_pop_status_activate(self, menuitem, data=None): #removes top message from stack - self.status_count -= 1 - self.statusbar.pop(self.context_id) - - def on_clear_status_activate(self, menuitem, data=None): #clears status stack - self.statusbar.remove_all(self.context_id) - self.status_count = 0 -# while (self.status_count > 0): -# self.statusbar.pop(self.context_id) -# self.status_count -= 1 - - def on_sfm_button_clicked(self, button, data=None): - # create an instance of the entry objects - # so we can get and set the text values - self.entry1 = self.builder.get_object("entry1") - self.entry2 = self.builder.get_object("entry2") - self.result1 = self.builder.get_object("result1") - - # get the text from the GtkEntry widget and convert - # it to a float value so we can calculate the result - self.sfm = float(self.entry1.get_text()) - self.diameter = float(self.entry2.get_text()) - - # calculate the result convert to an int to round the number - # then convert to a string to set the text in our label - # notice the math.pi constant is used in the calculation - self.rpm = str(int(self.sfm * ((12/math.pi)/self.diameter))) - - # debugging print - print "calculate rpm clicked" - - # set the result label with our results - self.result1.set_text(self.rpm) - - def on_gtk_new_activate(self, menuitem, data=None): - # debugging message - print 'File New selected' - - # create a label for the tab and using get_n_pages() to find out how - # many pages there is so the next page has a sequential number. - self.label1 = gtk.Label('Page ' + str(self.notebook.get_n_pages() + 1)) - - # create a label to put into the page - self.label2 = gtk.Label('Hello World') - # If you don't show the contents of the tab it won't show up - self.label2.show() - - # append a page with label5 as the contents and label5 as the tab - self.notebook.append_page(self.label2, self.label1) - - def on_notebook1_switch_page(self, notebook, page, page_num, data=None): - self.tab = notebook.get_nth_page(page_num) - self.label = notebook.get_tab_label(self.tab).get_label() - self.message_id = self.statusbar.push(0, self.label) - - - - # This is our init part where we connect the signals - def __init__(self): - self.gladefile = "test1.glade" # store the file name - self.builder = gtk.Builder() # create an instance of the gtk.Builder - self.builder.add_from_file(self.gladefile) # add the xml file to the Builder - - # This line does the magic of connecting the signals created in the Glade3 - # builder to our defines above. You must have one def for each signal if - # you use this line to connect the signals. - self.builder.connect_signals(self) - - #get widgets - self.window = self.builder.get_object("window1") - self.aboutdialog = self.builder.get_object("aboutdialog1") - self.statusbar = self.builder.get_object("statusbar") - self.notebook = self.builder.get_object("notebook1") - - self.window.show() # this shows the 'window1' object - - self.context_id = self.statusbar.get_context_id("status") #register and get statusbar context_id for description "status" - self.status_count = 0 #count of messages pushed - -# If this is run stand alone execute the following after the 'if' -# If this class is imported into another program the code after the 'if' will -# not run. This makes the code more flexible. -if __name__ == "__main__": - main = Buglump() # create an instance of our class - gtk.main() # run the darn thing \ No newline at end of file diff --git a/dstatInterface/interface/acv.py b/dstatInterface/interface/acv.py deleted file mode 100644 index 214af8345b8acbc345fc3bd2eb67be0a4e946565..0000000000000000000000000000000000000000 --- a/dstatInterface/interface/acv.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python - -import gtk - -class acv: - def __init__(self): - self.builder = gtk.Builder() - self.builder.add_from_file('interface/acv.glade') - self.builder.connect_signals(self) - - self.start_entry = self.builder.get_object('start_entry') - self.stop_entry = self.builder.get_object('stop_entry') - self.slope_entry = self.builder.get_object('slope_entry') - self.amplitude_entry = self.builder.get_object('amplitude_entry') - self.freq_entry = self.builder.get_object('freq_entry') \ No newline at end of file diff --git a/dstatInterface/interface/chronoamp.py b/dstatInterface/interface/chronoamp.py deleted file mode 100644 index 0eb3b090969634dce88602626b2a415c92da88c6..0000000000000000000000000000000000000000 --- a/dstatInterface/interface/chronoamp.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python - -import gtk - -class chronoamp: - def label_set_func(self, tree_column, cell, model, iter): - info = model.get_value(iter, 1) - cell.set_property("text", info) - - def __init__(self): - self.builder = gtk.Builder() - self.builder.add_from_file('interface/chronoamp.glade') - self.builder.connect_signals(self) - - self.statusbar = self.builder.get_object('statusbar') - self.potential = self.builder.get_object('potential_entry') - self.time = self.builder.get_object('time_entry') - self.model = self.builder.get_object('ca_list') - self.treeview = self.builder.get_object('treeview') - - self.cell_renderer = gtk.CellRendererText() - - self.treeview.insert_column_with_attributes(-1, "Time", self.cell_renderer, text=1).set_expand(True) - self.treeview.insert_column_with_attributes(-1, "Potential", self.cell_renderer, text=0).set_expand(True) - - self.treeviewselection = self.treeview.get_selection() - self.treeviewselection.set_mode(gtk.SELECTION_MULTIPLE) - - def on_add_button_clicked(self, widget): - self.statusbar.remove_all(0) - - try: - potential = int(self.potential.get_text()) - time = int(self.time.get_text()) - - if (potential > 1499 or potential < -1500): - raise ValueError("Potential out of range") - if (time < 1 or time > 65535): - raise ValueError("Time out of range") - - self.model.append([potential, time]) - - except ValueError as e: - self.statusbar.push(0, str(e)) - except TypeError as e: - self.statusbar.push(0, str(e)) - - def on_remove_button_clicked(self, widget): - self.selected_rows = list(self.treeviewselection.get_selected_rows()[1]) #returns 2-tuple: treemodel, list of paths selected rows - - self.referencelist = [] - - for i in self.selected_rows: - x=gtk.TreeRowReference(self.model, i) - self.referencelist.append(x) - - for i in self.referencelist: - self.model.remove(self.model.get_iter(i.get_path())) diff --git a/dstatInterface/interface/cv.py b/dstatInterface/interface/cv.py deleted file mode 100644 index 0aadf29d6e6f46e12793d5916514600dc9448da7..0000000000000000000000000000000000000000 --- a/dstatInterface/interface/cv.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python - -import gtk - -class cv: - def __init__(self): - self.builder = gtk.Builder() - self.builder.add_from_file('interface/cv.glade') - self.builder.connect_signals(self) - - self.clean_mV = self.builder.get_object('clean_mV') - self.clean_s = self.builder.get_object('clean_s') - self.dep_mV = self.builder.get_object('dep_mV') - self.dep_s = self.builder.get_object('dep_s') - self.start_entry = self.builder.get_object('start_entry') - self.v1_entry = self.builder.get_object('v1_entry') - self.v2_entry = self.builder.get_object('v2_entry') - self.slope_entry = self.builder.get_object('slope_entry') - self.scans_entry = self.builder.get_object('scans_entry') \ No newline at end of file diff --git a/dstatInterface/interface/dpv.py b/dstatInterface/interface/dpv.py deleted file mode 100644 index deb81bf8c73c2f45f02a9264c3088fbcec6c069a..0000000000000000000000000000000000000000 --- a/dstatInterface/interface/dpv.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python - -import gtk - -class dpv: - def __init__(self): - self.builder = gtk.Builder() - self.builder.add_from_file('interface/dpv.glade') - self.builder.connect_signals(self) - - self.clean_mV = self.builder.get_object('clean_mV') - self.clean_s = self.builder.get_object('clean_s') - self.dep_mV = self.builder.get_object('dep_mV') - self.dep_s = self.builder.get_object('dep_s') - self.start_entry = self.builder.get_object('start_entry') - self.stop_entry = self.builder.get_object('stop_entry') - self.step_entry = self.builder.get_object('step_entry') - self.pulse_entry = self.builder.get_object('pulse_entry') - self.period_entry = self.builder.get_object('period_entry') - self.width_entry = self.builder.get_object('width_entry') \ No newline at end of file diff --git a/dstatInterface/interface/lsv.py b/dstatInterface/interface/lsv.py deleted file mode 100644 index 990ca640c8c4471d4bae47205d79302bb72c7000..0000000000000000000000000000000000000000 --- a/dstatInterface/interface/lsv.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python - -import gtk - -class lsv: - def __init__(self): - self.builder = gtk.Builder() - self.builder.add_from_file('interface/lsv.glade') - self.builder.connect_signals(self) - - - self.clean_mV = self.builder.get_object('clean_mV') - self.clean_s = self.builder.get_object('clean_s') - self.dep_mV = self.builder.get_object('dep_mV') - self.dep_s = self.builder.get_object('dep_s') - self.start_entry = self.builder.get_object('start_entry') - self.stop_entry = self.builder.get_object('stop_entry') - self.slope_entry = self.builder.get_object('slope_entry') \ No newline at end of file diff --git a/dstatInterface/interface/pd.py b/dstatInterface/interface/pd.py deleted file mode 100644 index bd6652d821b15e513ef6eafb9ddf6c1fabd91d2b..0000000000000000000000000000000000000000 --- a/dstatInterface/interface/pd.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env python - -import gtk - -class pd: - def __init__(self): - self.builder = gtk.Builder() - self.builder.add_from_file('interface/pd.glade') - self.builder.connect_signals(self) - - self.voltage_entry = self.builder.get_object('voltage_entry') - self.time_entry = self.builder.get_object('time_entry') \ No newline at end of file diff --git a/dstatInterface/interface/swv.py b/dstatInterface/interface/swv.py deleted file mode 100644 index a78c44b9ec1cd518c23a5b9c14417a68d492daec..0000000000000000000000000000000000000000 --- a/dstatInterface/interface/swv.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python - -import gtk - -class swv: - def __init__(self): - self.builder = gtk.Builder() - self.builder.add_from_file('interface/swv.glade') - self.builder.connect_signals(self) - - self.clean_mV = self.builder.get_object('clean_mV') - self.clean_s = self.builder.get_object('clean_s') - self.dep_mV = self.builder.get_object('dep_mV') - self.dep_s = self.builder.get_object('dep_s') - self.start_entry = self.builder.get_object('start_entry') - self.stop_entry = self.builder.get_object('stop_entry') - self.step_entry = self.builder.get_object('step_entry') - self.pulse_entry = self.builder.get_object('pulse_entry') - self.freq_entry = self.builder.get_object('freq_entry') - self.cyclic_checkbutton = self.builder.get_object('cyclic_checkbutton') - self.scans_entry = self.builder.get_object('scans_entry') \ No newline at end of file diff --git a/dstatInterface/mpl.py b/dstatInterface/mpl.py deleted file mode 100644 index 0d57177bfb29cede8b4e2f9160643b568737cf8c..0000000000000000000000000000000000000000 --- a/dstatInterface/mpl.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python - -import sys, serial, io -import numpy as np -import matplotlib -import gtk -from time import sleep -from collections import deque -from matplotlib import pyplot as plt - - -# class that holds analog data for N samples -class AnalogData: - # constr - def __init__(self): - self.ax = [] - self.ay = [] - self.first = 1 - - # add data - def add(self, data): - if self.first == 1: - self.first = 0 - return - assert(len(data) == 2) - self.ax.append(data[0]) - self.ay.append(data[1]) - - # clear data - def clear(self): - self.first = 1 - self.ax = [] - self.ay = [] - - -# plot class -class AnalogPlot: - - # constr - def __init__(self, analogData): - self.i = 0 - # set plot to animated - plt.ion() #interactive mode on - plt.autoscale(True,True,True) - - self.line = plt.plot(analogData.ax,analogData.ay) - - # update plot - def update(self, analogData): - if self.i < 5: - self.i += 1 - return - plt.setp(self.line,xdata=analogData.ax, ydata=analogData.ay) - ax = plt.gca() - - # recompute the ax.dataLim - ax.relim() - # update ax.viewLim using the new dataLim - ax.autoscale_view() - plt.draw() - self.i=0 - -# main() function -def main(): -# # expects 1 arg - serial port string -# if(len(sys.argv) != 2): -# print 'Example usage: python showdata.py "/dev/tty.usbmodem411"' -# exit(1) - - #strPort = '/dev/tty.usbserial-A7006Yqh' - #strPort = sys.argv[1]; - strPort = '/dev/cu.usbmodem12...E1' - - # open serial port - ser = serial.Serial(strPort, 1024000,timeout=2) - sio = io.TextIOWrapper(io.BufferedRWPair(ser,ser,buffer_size=1), - newline = '\n', - line_buffering = True) - ser.write("ck") - - # plot parameters - digiData = AnalogData() - digiPlot = AnalogPlot(digiData) - - try: - while True: - output = raw_input('Commands:') - ser.flushInput() #clear input buffer - digiData.clear() #clear old data - ser.write(output) - print output - - while True: - for line in ser: - print line - if line.lstrip().startswith("no"): - ser.flushInput() - break - if not (line.isspace() or line.lstrip().startswith('#')): - #print line - data = [float(val) for val in line.split()] - if(len(data) == 2): - digiData.add(data) - digiPlot.update(digiData) - - break -# if not line.lstrip().startswith('#'): -# data = [float(val) for val in line.split()] -## if(len(data) == 2): -## analogData.add(data) -## analogPlot.update(analogData) -# block.append(line) -# print line -# print block - except KeyboardInterrupt: - print 'exiting' - # close serial - ser.flush() - ser.close() - -# call main -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/dstatInterface/mpltest.py b/dstatInterface/mpltest.py deleted file mode 100644 index ea8bc7585a474746f87782ce287df979f53d0beb..0000000000000000000000000000000000000000 --- a/dstatInterface/mpltest.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python -""" - show how to add a matplotlib FigureCanvasGTK or FigureCanvasGTKAgg widget to a - gtk.Window - """ -import gtk -from matplotlib.figure import Figure -from matplotlib import pyplot as plt -import numpy as np - -#from matplotlib.backends.backend_gtkcairo import FigureCanvasGTKCairo as FigureCanvas -#from matplotlib.backends.backend_gtkcairo import NavigationToolbar2Cairo as NavigationToolbar -from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas -from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar - -class plotbox: - def __init__(self, plotwindow_instance): - self.figure = Figure() - self.figure.subplots_adjust(left=0.07, bottom=0.07, right=0.96, top=0.96) - self.axe1 = self.figure.add_subplot(111) - - self.lines = self.axe1.plot([0,1], [0,1]) - - self.axe1.ticklabel_format(style='sci', scilimits=(0,3), useOffset=False, axis='y') - - self.canvas = FigureCanvas(self.figure) - self.win = gtk.Window() - self.vbox = gtk.VBox() - self.win.add(self.vbox) - self.vbox.pack_start(self.canvas) - self.toolbar = NavigationToolbar(self.canvas, self.win) - self.vbox.pack_start(self.toolbar, False, False) - self.vbox.reparent(plotwindow_instance) - - def clearall(self): - for i in self.lines: - i.remove() - self.lines = self.axe1.plot([0,1], [0,1]) - - def clearline(self, line_number): - self.lines[line_number].remove() - self.lines.pop(line_number) - - def addline(self): - self.lines.append(self.axe1.plot([0,1], [0,1])[0]) - - def updateline(self, Experiment, line_number): - divisor = len(Experiment.data[1+line_number*2]) // 2000 + 1 #limits display to 2000 data points per line - - self.lines[line_number].set_ydata(Experiment.data[1+line_number*2][1::divisor]) - self.lines[line_number].set_xdata(Experiment.data[line_number*2][1::divisor]) - - def changetype(self, Experiment): - self.axe1.set_xlabel(Experiment.xlabel) - self.axe1.set_ylabel(Experiment.ylabel) - self.axe1.set_xlim(Experiment.xmin, Experiment.xmax) - - self.figure.canvas.draw() - - def redraw(self): - self.axe1.relim() - self.axe1.autoscale(True, axis = 'y') - self.figure.canvas.draw() - - return True -