Skip to content
...@@ -29,15 +29,17 @@ except ImportError: ...@@ -29,15 +29,17 @@ except ImportError:
print "ERR: GTK not available" print "ERR: GTK not available"
sys.exit(1) sys.exit(1)
import interface.exp_int as exp from . import exp_int
logger = logging.getLogger(__name__)
logger = logging.getLogger("dstat.interface.exp_window")
class Experiments(GObject.Object): class Experiments(GObject.Object):
__gsignals__ = { __gsignals__ = {
'run_utility': (GObject.SIGNAL_RUN_FIRST, None, ()), 'run_utility': (GObject.SIGNAL_RUN_FIRST, None, ()),
'done_utility': (GObject.SIGNAL_RUN_FIRST, None, ()) 'done_utility': (GObject.SIGNAL_RUN_FIRST, None, ())
} }
def __init__(self, builder): def __init__(self, builder):
super(Experiments,self).__init__() super(Experiments,self).__init__()
self.builder = builder self.builder = builder
...@@ -46,12 +48,13 @@ class Experiments(GObject.Object): ...@@ -46,12 +48,13 @@ class Experiments(GObject.Object):
# (experiment index in UI, experiment) # (experiment index in UI, experiment)
classes = {c.id : c() # Make class instances classes = {c.id : c() # Make class instances
for _, c in inspect.getmembers(exp, inspect.isclass) for _, c in inspect.getmembers(exp_int, inspect.isclass)
if issubclass(c, exp.ExpInterface) if issubclass(c, exp_int.ExpInterface)
and c is not exp.ExpInterface and c is not exp_int.ExpInterface
} }
self.classes = OrderedDict(sorted(classes.items())) self.classes = OrderedDict(sorted(classes.items()))
# fill exp_section # fill exp_section
exp_section = self.builder.get_object('exp_section_box') exp_section = self.builder.get_object('exp_section_box')
self.containers = {} self.containers = {}
...@@ -86,6 +89,9 @@ class Experiments(GObject.Object): ...@@ -86,6 +89,9 @@ class Experiments(GObject.Object):
self.set_exp(self.expcombobox.get_active_id()) self.set_exp(self.expcombobox.get_active_id())
def setup_exp(self, parameters): def setup_exp(self, parameters):
"""Takes parameters.
Returns experiment instance.
"""
exp = self.classes[self.expcombobox.get_active_id()] exp = self.classes[self.expcombobox.get_active_id()]
try: try:
exp.param_test(parameters) exp.param_test(parameters)
...@@ -94,7 +100,7 @@ class Experiments(GObject.Object): ...@@ -94,7 +100,7 @@ class Experiments(GObject.Object):
"Experiment {} has no defined parameter test.".format( "Experiment {} has no defined parameter test.".format(
exp.name) exp.name)
) )
return exp.experiment(parameters) return exp.get_experiment(parameters)
def hide_exps(self): def hide_exps(self):
for key in self.containers: for key in self.containers:
...@@ -117,4 +123,7 @@ class Experiments(GObject.Object): ...@@ -117,4 +123,7 @@ class Experiments(GObject.Object):
return self.classes[experiment].params return self.classes[experiment].params
def set_params(self, experiment, parameters): def set_params(self, experiment, parameters):
try:
self.classes[experiment].params = parameters self.classes[experiment].params = parameters
except KeyError as e:
logger.warning("Tried to load inavlid experiment with id %s", e.args)
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from ..dstat import state, dfu
from ..dstat.comm import dstat_logger, exp_logger
import logging
import time
import serial
logger = logging.getLogger(__name__)
try:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
except ImportError:
print("ERR: GTK not available")
sys.exit(1)
class InfoDialog(object):
def __init__(self, parent, connect, signal='activate'):
self.parent = parent
connect.connect(signal, self.activate)
def activate(self, object=None, data=None):
self.dialog = Gtk.MessageDialog(self.parent, 0, Gtk.MessageType.INFO,
Gtk.ButtonsType.OK, "DStat Info")
self.dialog.format_secondary_text(
"PCB Version: {}\n".format(state.dstat_version.base_version) +
"Firmware Version: {}".format(state.firmware_version)
)
self.dialog.connect('response', self.destroy)
self.dialog.show()
def destroy(self, object=None, data=None):
self.dialog.destroy()
class ResetDialog(object):
def __init__(self, parent, connect, stop_callback, disconnect_callback, signal='activate'):
self.parent = parent
self.stop = stop_callback
self.disconnect = disconnect_callback
connect.connect(signal, self.activate)
def activate(self, object=None, data=None):
dialog = Gtk.MessageDialog(self.parent, 0, Gtk.MessageType.WARNING,
Gtk.ButtonsType.OK_CANCEL, "EEPROM Reset")
dialog.format_secondary_text("This will reset the DStat's EEPROM settings, then disconneect."
)
response = dialog.run()
if response == Gtk.ResponseType.OK:
self.dstat_reset_eeprom()
dialog.destroy()
def dstat_reset_eeprom(self):
"""Tries to contact DStat and resets EEPROM.
If no response, returns False, otherwise True.
"""
self.stop()
exp = EEPROMReset()
state.ser.start_exp(exp)
logger.info("Resetting DStat EEPROM…")
while True:
result = state.ser.get_proc(block=True)
if result in ('SERIAL_ERROR', 'DONE', 'ABORT'):
break
logger.info(result)
self.disconnect()
class EEPROMReset(object):
def __init__(self):
pass
def run(self, ser, ctrl_pipe, data_pipe):
status = None
try:
ser.write(b'!2\n')
exp_logger.info('!2')
for i in range(10):
if ser.readline().rstrip() == b"@ACK 2":
dstat_logger.info('@ACK 2')
ser.write(b'SD\n')
exp_logger.info('SD')
status = "DONE"
time.sleep(5)
break
else:
time.sleep(.5)
ser.reset_input_buffer()
ser.write(b'!2\n')
exp_logger.info('!2')
time.sleep(.1)
except UnboundLocalError as e:
status = "SERIAL_ERROR"
except serial.SerialException as e:
logger.error('SerialException: %s', e)
status = "SERIAL_ERROR"
finally:
return status
\ No newline at end of file
import logging import logging
logger = logging.getLogger("dstat.interface.plot_ui") logger = logging.getLogger(__name__)
try: try:
import gi import gi
......
...@@ -22,6 +22,9 @@ from __future__ import division, absolute_import, print_function, unicode_litera ...@@ -22,6 +22,9 @@ from __future__ import division, absolute_import, print_function, unicode_litera
import io import io
import os import os
import logging
logger = logging.getLogger(__name__)
try: try:
import gi import gi
...@@ -31,12 +34,9 @@ except ImportError: ...@@ -31,12 +34,9 @@ except ImportError:
print("ERR: GTK not available") print("ERR: GTK not available")
sys.exit(1) sys.exit(1)
import numpy as np import numpy as np
import logging
logger = logging.getLogger("dstat.interface.save")
from errors import InputError, VarError from ..errors import InputError, VarError
from params import save_params, load_params from ..params import save_params, load_params
def manSave(current_exp): def manSave(current_exp):
fcd = Gtk.FileChooserDialog("Save…", None, Gtk.FileChooserAction.SAVE, fcd = Gtk.FileChooserDialog("Save…", None, Gtk.FileChooserAction.SAVE,
......
...@@ -23,14 +23,14 @@ import yaml ...@@ -23,14 +23,14 @@ import yaml
from errors import InputError from errors import InputError
logger = logging.getLogger('dstat.params') logger = logging.getLogger(__name__)
def get_params(window): def get_params(window):
"""Fetches and returns dict of all parameters for saving.""" """Fetches and returns dict of all parameters for saving."""
selection = window.exp_window.expcombobox.get_active_id() selection = window.exp_window.expcombobox.get_active_id()
parameters = {} parameters = {'experiment_index' : selection}
parameters['experiment_index'] = selection
try: try:
parameters['version'] = window.version parameters['version'] = window.version
...@@ -46,6 +46,7 @@ def get_params(window): ...@@ -46,6 +46,7 @@ def get_params(window):
return parameters return parameters
def save_params(window, path): def save_params(window, path):
"""Fetches current params and saves to path.""" """Fetches current params and saves to path."""
logger.info("Save to: %s", path) logger.info("Save to: %s", path)
...@@ -54,6 +55,7 @@ def save_params(window, path): ...@@ -54,6 +55,7 @@ def save_params(window, path):
with open(path, 'w') as f: with open(path, 'w') as f:
yaml.dump(params, f) yaml.dump(params, f)
def load_params(window, path): def load_params(window, path):
"""Loads params from a path into UI elements.""" """Loads params from a path into UI elements."""
...@@ -68,6 +70,7 @@ def load_params(window, path): ...@@ -68,6 +70,7 @@ def load_params(window, path):
params = yaml.load(f) params = yaml.load(f)
set_params(window, params) set_params(window, params)
def set_params(window, params): def set_params(window, params):
window.adc_pot.params = params window.adc_pot.params = params
if 'experiment_index' in params: if 'experiment_index' in params:
......
...@@ -48,9 +48,11 @@ __all__ = ('getVersion') ...@@ -48,9 +48,11 @@ __all__ = ('getVersion')
import re import re
import subprocess import subprocess
import sys import sys
import os.path
import inspect
RELEASE_VERSION_FILE = '{}/RELEASE-VERSION'.format(
RELEASE_VERSION_FILE = 'RELEASE-VERSION' os.path.dirname(os.path.abspath(inspect.stack()[0][1])))
# http://www.python.org/dev/peps/pep-0386/ # http://www.python.org/dev/peps/pep-0386/
_PEP386_SHORT_VERSION_RE = r'\d+(?:\.\d+)+(?:(?:[abc]|rc)\d+(?:\.\d+)*)?' _PEP386_SHORT_VERSION_RE = r'\d+(?:\.\d+)+(?:(?:[abc]|rc)\d+(?:\.\d+)*)?'
......
__all__ = []
import pkgutil
import inspect
for loader, name, is_pkg in pkgutil.walk_packages(__path__):
module = loader.find_module(name).load_module(name)
for name, value in inspect.getmembers(module):
if name.startswith('__'):
continue
globals()[name] = value
__all__.append(name)
\ No newline at end of file
import time
import struct
from experiments.experiment_template import PlotBox, Experiment
class LSVExp(Experiment):
"""Linear Scan Voltammetry experiment"""
id = 'lsv'
def setup(self):
super(LSVExp, self).setup()
self.datatype = "linearData"
self.datalength = 2
self.databytes = 6 # uint16 + int32
self.plot_format['current_voltage']['xlims'] = (
int(self.parameters['start']),
int(self.parameters['stop'])
)
self.commands += "E"
self.commands[2] += "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(int(self.parameters['clean_mV'])*
(65536./3000)+32768))
self.commands[2] += " "
self.commands[2] += str(int(int(self.parameters['dep_mV'])*
(65536./3000)+32768))
self.commands[2] += " "
self.commands[2] += str(self.parameters['start'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['stop'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['slope'])
self.commands[2] += " "
\ No newline at end of file
# -*- mode: python -*-
a = Analysis(['interface_test.py'],
hiddenimports=[],
hookspath=None,
runtime_hooks=None)
glade_tree = Tree('./interface', prefix = 'interface', excludes=['*.py','*.pyc'])
drivers_tree = Tree('./drivers', prefix = 'drivers')
pyz = PYZ(a.pure)
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='interface_test.exe',
debug=False,
strip=None,
upx=True,
console=True )
coll = COLLECT(exe,
drivers_tree,
glade_tree,
a.binaries,
a.zipfiles,
a.datas,
strip=None,
upx=True,
name='interface_test')
...@@ -20,68 +20,74 @@ ...@@ -20,68 +20,74 @@
""" GUI Interface for Wheeler Lab DStat """ """ GUI Interface for Wheeler Lab DStat """
from __future__ import division, absolute_import, print_function, unicode_literals
import sys import sys
import os import os
import platform
import multiprocessing import multiprocessing
import uuid import uuid
from copy import deepcopy
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime from datetime import datetime
import logging
from pkg_resources import parse_version
from serial import SerialException
import zmq
from dstat_interface.core.utils.version import getVersion
from dstat_interface.core.experiments import idle, pot
from dstat_interface.core import params, analysis, dstat
from dstat_interface.core.dstat import boards
from dstat_interface.core.interface import (exp_window, adc_pot, plot_ui, data_view,
save, hw_info)
from dstat_interface.core.errors import InputError
from dstat_interface.core.plugin import DstatPlugin, get_hub_uri
# try:
# import pygtk
# pygtk.require('2.0')
# except ImportError:
# print "ERR: PyGTK 2.0 not available"
# sys.exit(1)
try: try:
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject from gi.repository import Gtk, GObject
except ImportError: except ImportError:
print "ERR: GTK not available" print("ERR: GTK not available")
sys.exit(1) sys.exit(1)
from serial import SerialException mod_dir = os.path.dirname(os.path.abspath(__file__))
import logging conf_path = os.path.join(os.path.expanduser("~"), '.dstat-interface')
os.chdir(os.path.dirname(os.path.abspath(sys.argv[0])))
from version import getVersion
import interface.save as save
import dstat_comm as comm
import experiments as exp
import interface.exp_window as exp_window
import interface.adc_pot as adc_pot
import interface.plot_ui
import interface.data_view
import plot
import params
import parameter_test
import analysis
import zmq
import state
from errors import InputError
from plugin import DstatPlugin, get_hub_uri # if __name__ == "__parents_main__": # Only runs for forking emulation on win
# sys.path.append(mod_dir)
# Setup Logging # Setup Logging
root_logger = logging.getLogger("dstat") logger = logging.getLogger(__name__)
root_logger.setLevel(level=logging.INFO) core_logger = logging.getLogger("dstat_interface.core")
loggers = [logger, core_logger]
log_handler = logging.StreamHandler() log_handler = logging.StreamHandler()
log_formatter = logging.Formatter( log_formatter = logging.Formatter(
fmt='%(asctime)s %(levelname)s: [%(name)s] %(message)s', fmt='%(asctime)s %(levelname)s: [%(name)s] %(message)s',
datefmt='%H:%M:%S' datefmt='%H:%M:%S'
) )
log_handler.setFormatter(log_formatter) log_handler.setFormatter(log_formatter)
root_logger.addHandler(log_handler)
logger = logging.getLogger("dstat.main") for log in loggers:
log.setLevel(level=logging.INFO)
log.addHandler(log_handler)
class Main(object): class Main(GObject.Object):
"""Main program """ """Main program """
__gsignals__ = {
b'exp_start': (GObject.SIGNAL_RUN_FIRST, None, ()),
b'exp_stop': (GObject.SIGNAL_RUN_FIRST, None, ())
}
def __init__(self): def __init__(self):
super(Main, self).__init__()
self.builder = Gtk.Builder() self.builder = Gtk.Builder()
self.builder.add_from_file('interface/dstatinterface.glade') self.builder.add_from_file(
os.path.join(mod_dir, 'core/interface/dstatinterface.glade'))
self.builder.connect_signals(self) self.builder.connect_signals(self)
self.cell = Gtk.CellRendererText() self.cell = Gtk.CellRendererText()
...@@ -92,6 +98,8 @@ class Main(object): ...@@ -92,6 +98,8 @@ class Main(object):
self.aboutdialog = self.builder.get_object('aboutdialog1') self.aboutdialog = self.builder.get_object('aboutdialog1')
self.stopbutton = self.builder.get_object('pot_stop') self.stopbutton = self.builder.get_object('pot_stop')
self.startbutton = self.builder.get_object('pot_start') self.startbutton = self.builder.get_object('pot_start')
self.startbutton.set_sensitive(False)
self.exp_progressbar = self.builder.get_object('exp_progressbar')
self.adc_pot = adc_pot.adc_pot() self.adc_pot = adc_pot.adc_pot()
self.error_context_id = self.statusbar.get_context_id("error") self.error_context_id = self.statusbar.get_context_id("error")
...@@ -111,8 +119,8 @@ class Main(object): ...@@ -111,8 +119,8 @@ class Main(object):
# Setup Plots # Setup Plots
self.plot_notebook = self.builder.get_object('plot_notebook') self.plot_notebook = self.builder.get_object('plot_notebook')
self.main_notebook = self.builder.get_object('main_notebook') self.main_notebook = self.builder.get_object('main_notebook')
self.data_view = interface.data_view.DataPage(self.main_notebook) self.data_view = data_view.DataPage(self.main_notebook)
self.info_page = interface.data_view.InfoPage(self.main_notebook) self.info_page = data_view.InfoPage(self.main_notebook)
# fill adc_pot_box # fill adc_pot_box
self.adc_pot_box = self.builder.get_object('gain_adc_box') self.adc_pot_box = self.builder.get_object('gain_adc_box')
...@@ -127,9 +135,8 @@ class Main(object): ...@@ -127,9 +135,8 @@ class Main(object):
self.serial_combobox = self.builder.get_object('serial_combobox') self.serial_combobox = self.builder.get_object('serial_combobox')
self.serial_combobox.pack_start(self.cell, True) self.serial_combobox.pack_start(self.cell, True)
self.serial_combobox.add_attribute(self.cell, 'text', 0) self.serial_combobox.add_attribute(self.cell, 'text', 0)
self.serial_liststore = self.builder.get_object('serial_liststore') self.serial_liststore = self.builder.get_object('serial_liststore')
self.serial_devices = comm.SerialDevices() self.serial_devices = dstat.comm.SerialDevices()
for i in self.serial_devices.ports: for i in self.serial_devices.ports:
self.serial_liststore.append([i]) self.serial_liststore.append([i])
...@@ -140,6 +147,29 @@ class Main(object): ...@@ -140,6 +147,29 @@ class Main(object):
self.mainwindow = self.builder.get_object('window1') self.mainwindow = self.builder.get_object('window1')
self.menu_dstat_info = self.builder.get_object('menu_dstat_info')
self.menu_dstat_info.set_sensitive(False)
self.dstat_info_window = hw_info.InfoDialog(
parent=self.mainwindow,
connect=self.menu_dstat_info
)
self.menu_dstat_fw = self.builder.get_object('menu_dstat_fw')
self.menu_dstat_fw.set_sensitive(False)
self.dstat_fw_window = dstat.dfu.FWDialog(
parent=self.mainwindow,
connect=self.menu_dstat_fw,
stop_callback=self.stop_ocp,
disconnect_callback=self.on_serial_disconnect_clicked
)
self.menu_dstat_reset = self.builder.get_object('menu_dstat_reset')
self.menu_dstat_reset.set_sensitive(False)
self.menu_dstat_reset_window = hw_info.ResetDialog(
parent=self.mainwindow,
connect=self.menu_dstat_reset,
stop_callback=self.stop_ocp,
disconnect_callback=self.on_serial_disconnect_clicked
)
# Set Version Strings # Set Version Strings
try: try:
ver = getVersion() ver = getVersion()
...@@ -152,7 +182,6 @@ class Main(object): ...@@ -152,7 +182,6 @@ class Main(object):
self.mainwindow.show_all() self.mainwindow.show_all()
self.exp_window.hide_exps() self.exp_window.hide_exps()
self.expnumber = 0 self.expnumber = 0
self.connected = False self.connected = False
...@@ -186,69 +215,100 @@ class Main(object): ...@@ -186,69 +215,100 @@ class Main(object):
def quit(self): def quit(self):
"""Disconnect and save parameters on quit.""" """Disconnect and save parameters on quit."""
params.save_params(self, 'last_params.yml')
try:
params.save_params(self, os.path.join(conf_path, 'last_params.yml'))
self.on_serial_disconnect_clicked() self.on_serial_disconnect_clicked()
except KeyError:
pass
mainloop.quit() mainloop.quit()
def on_gtk_about_activate(self, menuitem, data=None): def on_gtk_about_activate(self, menuitem, data=None):
"""Display the about window.""" """Display the about window."""
self.response = self.aboutdialog.run() # waits for user to click close self.aboutdialog.run() # waits for user to click close
self.aboutdialog.hide() self.aboutdialog.hide()
def on_menu_analysis_options_activate(self, menuitem, data=None): def on_menu_analysis_options_activate(self, menuitem, data=None):
self.analysis_opt_window.show() self.analysis_opt_window.show()
def on_serial_refresh_clicked(self, data=None): def on_serial_refresh_clicked(self, data=None):
"""Refresh list of serial devices.""" """Refresh list of serial devices."""
try:
self.serial_devices.refresh() self.serial_devices.refresh()
self.serial_liststore.clear() self.serial_liststore.clear()
except ValueError:
logger.warning("No DStats found")
for i in self.serial_devices.ports: for i in self.serial_devices.ports:
self.serial_liststore.append([i]) self.serial_liststore.append([i])
def on_serial_connect_clicked(self, data=None): def on_serial_connect_clicked(self, widget):
"""Connect and retrieve DStat version.""" """Connect and retrieve DStat version."""
selection = self.serial_combobox.get_active_iter()
if selection is None:
return
if widget is self.serial_pmt_connect:
self.pmt_mode = True
self.adc_pot.ui['short_true'].set_active(True)
self.adc_pot.ui['short_true'].set_sensitive(False)
try: try:
self.serial_connect.set_sensitive(False) self.serial_connect.set_sensitive(False)
state.dstat_version = comm.version_check(self.serial_liststore.get_value( self.serial_pmt_connect.set_sensitive(False)
self.serial_combobox.get_active_iter(), 0)) dstat.comm.version_check(
self.serial_liststore.get_value(selection, 0)
)
self.statusbar.remove_all(self.error_context_id) dstat.state.board_instance = boards.find_board(
dstat.state.dstat_version)()
if not len(state.dstat_version) == 2: self.statusbar.remove_all(self.error_context_id)
self.statusbar.push(self.error_context_id,
"Communication Error")
return
else: self.adc_pot.set_version()
self.adc_pot.set_version(state.dstat_version) self.statusbar.push(
self.statusbar.push(self.error_context_id, self.message_context_id,
"".join(["DStat version: ", "DStat version: {}".format(
str(state.dstat_version[0]), dstat.state.dstat_version.base_version)
".", str(state.dstat_version[1])])
) )
comm.read_settings() dstat.comm.read_settings()
try:
if dstat.state.settings['dac_units_true'][1] != b'1':
dstat.state.settings['dac_units_true'][1] = b'1'
dstat.comm.write_settings()
except KeyError:
logger.warning("Connected DStat does not support sending DAC units.")
dialog = Gtk.MessageDialog(
self.window, 0, Gtk.MessageType.WARNING,
Gtk.ButtonsType.OK, "Connected DStat does not support sending DAC units." +
"Update firmware or set potentials will be incorrect!")
dialog.run()
dialog.destroy()
self.start_ocp() self.start_ocp()
self.connected = True self.connected = True
self.serial_connect.set_sensitive(False)
self.serial_pmt_connect.set_sensitive(False)
self.serial_disconnect.set_sensitive(True) self.serial_disconnect.set_sensitive(True)
except AttributeError as err: except:
logger.warning("AttributeError: %s", err)
self.serial_connect.set_sensitive(True)
except TypeError as err:
logger.warning("TypeError: %s", err)
self.serial_connect.set_sensitive(True) self.serial_connect.set_sensitive(True)
self.serial_pmt_connect.set_sensitive(True)
self.adc_pot.ui['short_true'].set_sensitive(True)
raise
if self.params_loaded == False: if self.params_loaded == False:
try: try:
params.load_params(self, 'last_params.yml') try:
os.makedirs(conf_path)
except OSError:
if not os.path.isdir(conf_path):
raise
params.load_params(self,
os.path.join(conf_path, 'last_params.yml')
)
except IOError: except IOError:
logger.info("No previous parameters found.") logger.info("No previous parameters found.")
...@@ -262,46 +322,53 @@ class Main(object): ...@@ -262,46 +322,53 @@ class Main(object):
self.stop_ocp() self.stop_ocp()
else: else:
self.on_pot_stop_clicked() self.on_pot_stop_clicked()
state.ser.send_ctrl("DISCONNECT") dstat.state.ser.disconnect()
state.ser.disconnect()
except AttributeError as err: except AttributeError as err:
logger.warning("AttributeError: %s", err) logger.warning("AttributeError: %s", err)
pass pass
if self.pmt_mode is True:
self.adc_pot.ui['short_true'].set_sensitive(True)
self.pmt_mode = False self.pmt_mode = False
self.connected = False self.connected = False
self.serial_connect.set_sensitive(True) self.serial_connect.set_sensitive(True)
self.serial_pmt_connect.set_sensitive(True) self.serial_pmt_connect.set_sensitive(True)
self.serial_disconnect.set_sensitive(False) self.serial_disconnect.set_sensitive(False)
self.startbutton.set_sensitive(False)
self.stopbutton.set_sensitive(False)
self.menu_dstat_info.set_sensitive(False)
self.menu_dstat_fw.set_sensitive(False)
self.menu_dstat_reset.set_sensitive(False)
self.adc_pot.ui['short_true'].set_sensitive(True) self.adc_pot.ui['short_true'].set_sensitive(True)
def on_pmt_mode_clicked(self, data=None): dstat.state.reset()
"""Connect in PMT mode"""
self.pmt_mode = True
self.adc_pot.ui['short_true'].set_active(True)
self.adc_pot.ui['short_true'].set_sensitive(False)
self.on_serial_connect_clicked()
def start_ocp(self, data=None): def start_ocp(self, data=None):
"""Start OCP measurements.""" """Start OCP measurements."""
if dstat.state.dstat_version >= parse_version('1.2'):
if state.dstat_version[0] >= 1 and state.dstat_version[1] >= 2:
# Flush data pipe # Flush data pipe
state.ser.flush_data() dstat.state.ser.flush_data()
if self.pmt_mode == True: if self.pmt_mode is True:
logger.info("Start PMT idle mode") logger.info("Start PMT idle mode")
state.ser.start_exp(exp.PMTIdle()) dstat.state.ser.start_exp(idle.PMTIdle())
self.ocp_is_running = True
self.ocp_proc = (GObject.timeout_add(250, self.ocp_running_proc)
,
)
else: else:
logger.info("Start OCP") logger.info("Start OCP")
state.ser.start_exp(exp.OCPExp()) dstat.state.ser.start_exp(idle.OCPExp())
self.ocp_proc = (GObject.timeout_add(300, self.ocp_running_data), self.ocp_proc = (GObject.timeout_add(300, self.ocp_running_data),
GObject.timeout_add(250, self.ocp_running_proc) GObject.timeout_add(250, self.ocp_running_proc)
) )
self.ocp_is_running = True self.ocp_is_running = False
GObject.timeout_add(100, self.ocp_assert) # Check if getting data
else: else:
logger.info("OCP measurements not supported on v1.1 boards.") logger.info("OCP measurements not supported on v1.1 boards.")
...@@ -310,23 +377,37 @@ class Main(object): ...@@ -310,23 +377,37 @@ class Main(object):
def stop_ocp(self, data=None): def stop_ocp(self, data=None):
"""Stop OCP measurements.""" """Stop OCP measurements."""
if state.dstat_version[0] >= 1 and state.dstat_version[1] >= 2: if dstat.state.dstat_version >= parse_version('1.2'):
if self.pmt_mode == True: if self.pmt_mode == True:
logger.info("Stop PMT idle mode") logger.info("Stop PMT idle mode")
else: else:
logger.info("Stop OCP") logger.info("Stop OCP")
state.ser.send_ctrl('a') dstat.state.ser.send_ctrl('a')
for i in self.ocp_proc: for i in self.ocp_proc:
GObject.source_remove(i) GObject.source_remove(i)
while self.ocp_running_proc(): while self.ocp_running_proc():
pass pass
self.ocp_is_running = False
self.ocp_disp.set_text("") self.ocp_disp.set_text("")
self.ocp_is_running = False
self.startbutton.set_sensitive(True)
self.menu_dstat_info.set_sensitive(True)
self.menu_dstat_fw.set_sensitive(True)
self.menu_dstat_reset.set_sensitive(True)
else: else:
logger.error("OCP measurements not supported on v1.1 boards.") logger.error("OCP measurements not supported on v1.1 boards.")
return return
def ocp_assert(self):
if self.ocp_is_running:
self.startbutton.set_sensitive(True)
self.menu_dstat_info.set_sensitive(True)
self.menu_dstat_fw.set_sensitive(True)
self.menu_dstat_reset.set_sensitive(True)
return False
else:
return True
def ocp_running_data(self): def ocp_running_data(self):
"""Receive OCP value from experiment process and update ocp_disp field """Receive OCP value from experiment process and update ocp_disp field
...@@ -337,7 +418,7 @@ class Main(object): ...@@ -337,7 +418,7 @@ class Main(object):
""" """
try: try:
incoming = state.ser.get_data() incoming = dstat.state.ser.get_data()
while incoming is not None: while incoming is not None:
if isinstance(incoming, basestring): # test if incoming is str if isinstance(incoming, basestring): # test if incoming is str
self.on_serial_disconnect_clicked() self.on_serial_disconnect_clicked()
...@@ -348,10 +429,11 @@ class Main(object): ...@@ -348,10 +429,11 @@ class Main(object):
"{0:.3f}".format(incoming), "{0:.3f}".format(incoming),
" V"]) " V"])
self.ocp_disp.set_text(data) self.ocp_disp.set_text(data)
self.ocp_is_running = True
except ValueError: except ValueError:
pass pass
incoming = state.ser.get_data() incoming = dstat.state.ser.get_data()
return True return True
...@@ -370,16 +452,16 @@ class Main(object): ...@@ -370,16 +452,16 @@ class Main(object):
""" """
try: try:
proc_buffer = state.ser.get_proc() proc_buffer = dstat.state.ser.get_proc()
while proc_buffer is not None: while proc_buffer is not None:
logger.debug("ocp_running_proc: %s", proc_buffer) logger.debug("ocp_running_proc: %s", proc_buffer)
if proc_buffer in ["DONE", "SERIAL_ERROR", "ABORT"]: if proc_buffer in ["DONE", "SERIAL_ERROR", "ABORT"]:
if proc_buffer == "SERIAL_ERROR": if proc_buffer == "SERIAL_ERROR":
self.on_serial_disconnect_clicked() self.on_serial_disconnect_clicked()
state.ser.flush_data() dstat.state.ser.flush_data()
return False return False
proc_buffer = state.ser.get_proc() proc_buffer = dstat.state.ser.get_proc()
return True return True
except EOFError: except EOFError:
...@@ -417,7 +499,7 @@ class Main(object): ...@@ -417,7 +499,7 @@ class Main(object):
self.stop_ocp() self.stop_ocp()
self.statusbar.remove_all(self.error_context_id) self.statusbar.remove_all(self.error_context_id)
state.ser.flush_data() dstat.state.ser.flush_data()
parameters = {} parameters = {}
parameters['metadata'] = self.metadata parameters['metadata'] = self.metadata
...@@ -440,30 +522,28 @@ class Main(object): ...@@ -440,30 +522,28 @@ class Main(object):
self.stopbutton.set_sensitive(True) self.stopbutton.set_sensitive(True)
self.statusbar.remove_all(self.error_context_id) self.statusbar.remove_all(self.error_context_id)
try:
del self.current_exp
except AttributeError:
pass
callbacks = {'experiment_done' : self.experiment_done,
'progress_update' : self.progress_update}
self.current_exp = self.exp_window.setup_exp(parameters) self.current_exp = self.exp_window.setup_exp(parameters)
interface.plot_ui.replace_notebook_exp( plot_ui.replace_notebook_exp(
self.plot_notebook, self.current_exp, self.window self.plot_notebook, self.current_exp, self.window
) )
for plot in self.current_exp.plots:
plot.changetype(self.current_exp)
self.data_view.clear_exps() self.data_view.clear_exps()
self.info_page.clear() self.info_page.clear()
state.ser.start_exp(self.current_exp) dstat.state.ser.start_exp(self.current_exp)
# Flush data pipe # Flush data pipe
state.ser.flush_data() dstat.state.ser.flush_data()
self.current_exp.setup_loops(callbacks)
self.plot_proc = GObject.timeout_add(200,
self.experiment_running_plot)
self.experiment_proc = (
GObject.idle_add(self.experiment_running_data),
GObject.idle_add(self.experiment_running_proc)
)
return experiment_id return experiment_id
except ValueError as i: except ValueError as i:
...@@ -501,6 +581,12 @@ class Main(object): ...@@ -501,6 +581,12 @@ class Main(object):
exceptions() exceptions()
raise raise
def progress_update(self, widget, progress):
if 0 <= progress <= 1:
self.exp_progressbar.set_fraction(progress)
else:
self.exp_progressbar.pulse()
def experiment_running_data(self): def experiment_running_data(self):
"""Receive data from experiment process and add to """Receive data from experiment process and add to
current_exp.data['data]. current_exp.data['data].
...@@ -512,27 +598,35 @@ class Main(object): ...@@ -512,27 +598,35 @@ class Main(object):
function from GTK's queue. function from GTK's queue.
""" """
try: try:
incoming = state.ser.get_data() incoming = dstat.state.ser.get_data()
while incoming is not None: while incoming is not None:
try: try:
self.line, data = incoming self.line, data = incoming
if self.line > self.lastdataline: if self.line > self.lastdataline:
newline = True newline = True
try:
logger.info("running scan_process()")
self.current_exp.scan_process(self.lastdataline)
except AttributeError:
pass
self.lastdataline = self.line self.lastdataline = self.line
else: else:
newline = False newline = False
self.current_exp.store_data(incoming, newline) self.current_exp.store_data(incoming, newline)
if newline:
self.experiment_running_plot()
except TypeError: except TypeError:
pass pass
incoming = state.ser.get_data() incoming = dstat.state.ser.get_data()
return True return True
except EOFError as err: except EOFError as err:
print err logger.error(err)
self.experiment_done() self.experiment_done()
return False return False
except IOError as err: except IOError as err:
print err logger.error(err)
self.experiment_done() self.experiment_done()
return False return False
...@@ -546,7 +640,14 @@ class Main(object): ...@@ -546,7 +640,14 @@ class Main(object):
function from GTK's queue. function from GTK's queue.
""" """
try: try:
proc_buffer = state.ser.get_proc() ctrl_buffer = dstat.state.ser.get_ctrl()
try:
if ctrl_buffer is not None:
self.current_exp.ctrl_loop(ctrl_buffer)
except AttributeError:
pass
proc_buffer = dstat.state.ser.get_proc()
if proc_buffer is not None: if proc_buffer is not None:
if proc_buffer in ["DONE", "SERIAL_ERROR", "ABORT"]: if proc_buffer in ["DONE", "SERIAL_ERROR", "ABORT"]:
self.experiment_done() self.experiment_done()
...@@ -574,53 +675,32 @@ class Main(object): ...@@ -574,53 +675,32 @@ class Main(object):
removed from GTK's queue. removed from GTK's queue.
""" """
for plot in self.current_exp.plots: for plot in self.current_exp.plots:
if self.line > self.lastline: if (plot.scan_refresh and self.line > self.lastline):
plot.addline() while self.line > self.lastline:
# make sure all of last line is added # make sure all of last line is added
plot.updateline(self.current_exp, self.lastline) plot.updateline(self.current_exp, self.lastline)
self.lastline = self.line self.lastline += 1
plot.updateline(self.current_exp, self.line)
plot.redraw()
else:
while self.line > self.lastline:
# make sure all of last line is added
plot.updateline(self.current_exp, self.lastline)
self.lastline += 1
plot.updateline(self.current_exp, self.line) plot.updateline(self.current_exp, self.line)
if plot.continuous_refresh is True or force_refresh is True: if plot.continuous_refresh is True or force_refresh is True:
plot.redraw() plot.redraw()
return True return True
def experiment_done(self): def experiment_done(self, widget=None):
"""Clean up after data acquisition is complete. Update plot and """Clean up after data acquisition is complete. Update plot and
copy data to raw data tab. Saves data if autosave enabled. copy data to raw data tab. Saves data if autosave enabled.
""" """
try: try:
self.current_exp.experiment_done()
GObject.source_remove(self.experiment_proc[0])
GObject.source_remove(self.plot_proc) # stop automatic plot update
self.experiment_running_plot(force_refresh=True)
# # Shutter stuff
# if (self.current_exp.parameters['shutter_true'] and
# self.current_exp.parameters['sync_true']):
# self.ft_plot.updateline(self.current_exp, 0)
# self.ft_plot.redraw()
#
# line_buffer = []
#
# for scan in self.current_exp.data['ft']:
# for dimension in scan:
# for i in range(len(dimension)):
# try:
# line_buffer[i] += "%s " % dimension[i]
# except IndexError:
# line_buffer.append("")
# line_buffer[i] += "%s " % dimension[i]
#
# for i in line_buffer:
# self.databuffer.insert_at_cursor("%s\n" % i)
# Run Analysis # Run Analysis
analysis.do_analysis(self.current_exp) analysis.do_analysis(self.current_exp)
self.current_exp.experiment_done()
# Write DStat commands # Write DStat commands
self.info_page.set_text(self.current_exp.get_info_text()) self.info_page.set_text(self.current_exp.get_info_text())
...@@ -668,17 +748,24 @@ class Main(object): ...@@ -668,17 +748,24 @@ class Main(object):
self.metadata = None # Reset metadata self.metadata = None # Reset metadata
self.spinner.stop() self.spinner.stop()
self.startbutton.set_sensitive(True) self.exp_progressbar.set_fraction(0)
self.stopbutton.set_sensitive(False) self.stopbutton.set_sensitive(False)
self.start_ocp() self.start_ocp()
self.completed_experiment_ids[self.active_experiment_id] =\ self.completed_experiment_ids[self.active_experiment_id] =\
datetime.utcnow() datetime.utcnow()
# Temporary fix for weird drawing bug on windows Gtk+3
if platform.system() == 'Windows':
position = self.window.get_position()
self.window.hide()
self.window.show()
self.window.move(*position)
def on_pot_stop_clicked(self, data=None): def on_pot_stop_clicked(self, data=None):
"""Stop current experiment. Signals experiment process to stop.""" """Stop current experiment. Signals experiment process to stop."""
try: try:
state.ser.stop_exp() dstat.state.ser.stop_exp()
except AttributeError: except AttributeError:
pass pass
......
mr_db @ 72481e58
Subproject commit 72481e585d92d0adab6537718a4d18164df1b445
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# DStat Interface - An interface for the open hardware DStat potentiostat
# Copyright (C) 2014 Michael D. M. Dryden -
# Wheeler Microfluidics Laboratory <http://microfluidics.utoronto.ca>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from errors import InputError
logger = logging.getLogger("dstat.parameter_test")
def lsv_test(params):
"""Test LSV parameters for sanity"""
test_parameters = ['clean_mV', 'dep_mV', 'clean_s', 'dep_s', 'start',
'stop', 'slope']
parameters = {}
for i in params:
if i in test_parameters:
parameters[i] = int(params[i])
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.")
if (parameters['dep_s'] < 0):
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.")
if (parameters['stop'] > 1499 or parameters['stop'] < -1500):
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.")
if parameters['start'] == parameters['stop']:
raise InputError(parameters['start'],
"Start cannot equal Stop.")
def cv_test(params):
"""Test CV parameters for sanity"""
test_parameters = ['clean_mV', 'dep_mV', 'clean_s', 'dep_s', 'start',
'stop', 'slope', 'v1', 'v2', 'scans']
parameters = {}
for i in params:
if i in test_parameters:
parameters[i] = int(params[i])
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.")
if (parameters['dep_s'] < 0):
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.")
if (parameters['slope'] > 2000 or parameters['slope'] < 1):
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.")
if (parameters['v2'] > 1499 or parameters['v2'] < -1500):
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.")
if parameters['v1'] == parameters['v2']:
raise InputError(parameters['v1'],
"Vertex 1 cannot equal Vertex 2.")
def swv_test(params):
"""Test SWV parameters for sanity"""
test_parameters = ['clean_mV', 'dep_mV', 'clean_s', 'dep_s', 'start',
'stop', 'step', 'pulse', 'freq']
parameters = {}
for i in params:
if i in test_parameters:
parameters[i] = int(params[i])
if params['cyclic_true'] :
if int(params['scans']) < 1:
raise InputError(params['scans'],
"Must have at least one scan.")
else:
params['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.")
if (parameters['clean_s'] < 0):
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.")
if (parameters['start'] > 1499 or parameters['start'] < -1500):
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.")
if (parameters['stop'] > 1499 or parameters['stop'] < -1500):
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.")
if (parameters['freq'] < 1 or parameters['freq'] > 1000):
raise InputError(parameters['freq'],
"Frequency parameter outside limits.")
if parameters['start'] == parameters['stop']:
raise InputError(parameters['start'],
"Start cannot equal Stop.")
def dpv_test(params):
"""Test DPV parameters for sanity"""
test_parameters = ['clean_mV', 'dep_mV', 'clean_s', 'dep_s', 'start',
'stop', 'step', 'pulse', 'period', 'width']
parameters = {}
for i in params:
if i in test_parameters:
parameters[i] = int(params[i])
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.")
if (parameters['dep_s'] < 0):
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.")
if (parameters['step'] > 200 or parameters['step'] < 1):
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.")
if (parameters['pulse'] > 150 or parameters['pulse'] < 1):
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.")
if (parameters['width'] < 1 or parameters['width'] > 1000):
raise InputError(parameters['width'],
"Width parameter outside limits.")
if parameters['period'] <= parameters['width']:
raise InputError(parameters['width'],
"Width must be less than period.")
if parameters['start'] == parameters['stop']:
raise InputError(parameters['start'],
"Start cannot equal Stop.")
def pd_test(parameters):
"""Test PD parameters for sanity"""
if (int(parameters['time']) <= 0):
raise InputError(parameters['time'],
"Time must be greater than zero.")
if (int(parameters['time']) > 65535):
raise InputError(parameters['time'],
"Time must fit in 16-bit counter.")
if (parameters['sync_true'] and parameters['shutter_true']):
if (float(parameters['sync_freq']) > 30 or
float(parameters['sync_freq']) <= 0):
raise InputError(parameters['sync_freq'],
"Frequency must be between 0 and 30 Hz.")
if (float(parameters['fft_start']) < 0 or
float(parameters['fft_start']) > float(parameters['time'])-1):
raise InputError(parameters['fft_start'],
"FFT must start between 0 and time-1.")
if float(parameters['fft_int']) < 0:
raise InputError(
parameters['fft_int'],
"Integral bandwidth must be greater than 0"
)
def pot_test(params):
"""Test POT parameters for sanity"""
test_parameters = ['time']
parameters = {}
for i in params:
if i in test_parameters:
parameters[i] = int(params[i])
if (int(parameters['time']) <= 0):
raise InputError(parameters['time'],
"Time must be greater than zero.")
if (int(parameters['time']) > 65535):
raise InputError(parameters['time'],
"Time must fit in 16-bit counter.")
\ No newline at end of file