From 97d643c273d2e423be966b79469b24481a680f89 Mon Sep 17 00:00:00 2001
From: "Michael D. M. Dryden" <mk.dryden@utoronto.ca>
Date: Sun, 5 Dec 2021 17:43:45 -0500
Subject: [PATCH] New trio/CLI only version

---
 MANIFEST.in                                   |  14 -
 conda-env.yml                                 | 137 ---
 dstat_interface/__init__.py                   |   4 +
 dstat_interface/core/analysis.py              | 194 ----
 dstat_interface/core/daemon.py                | 154 ++++
 dstat_interface/core/dstat-cli.py             | 135 +++
 dstat_interface/core/dstat-interface.bat      |   1 -
 dstat_interface/core/dstat.spec               |  25 -
 dstat_interface/core/dstat/__init__.py        |   3 -
 dstat_interface/core/dstat/boards.py          |  45 +-
 dstat_interface/core/dstat/comm.py            | 562 +++---------
 dstat_interface/core/dstat/dfu.py             | 375 --------
 .../core/dstat/experiment_handler.py          | 115 +++
 .../core/dstat/experiment_process.py          | 246 +++++
 dstat_interface/core/dstat/simulator.py       | 403 +++++++++
 dstat_interface/core/dstat/state.py           |  39 +-
 dstat_interface/core/dstat/test/test_comm.py  |  41 +
 dstat_interface/core/dstat/utility.py         | 123 +++
 dstat_interface/core/errors.py                |  47 -
 dstat_interface/core/experiments/__init__.py  |  17 -
 dstat_interface/core/experiments/cal.py       | 139 ---
 dstat_interface/core/experiments/chronoamp.py | 226 ++---
 dstat_interface/core/experiments/cv.py        |  68 --
 .../core/experiments/experiment_container.py  | 153 ++++
 .../core/experiments/experiment_loops.py      | 201 -----
 .../core/experiments/experiment_template.py   | 571 ------------
 dstat_interface/core/experiments/idle.py      |  40 -
 dstat_interface/core/experiments/lsv.py       | 182 ++--
 dstat_interface/core/experiments/pot.py       |  80 --
 dstat_interface/core/experiments/swv.py       | 367 +++-----
 dstat_interface/core/interface/__init__.py    |   0
 dstat_interface/core/interface/acv.glade      | 445 ---------
 dstat_interface/core/interface/adc_pot.glade  | 412 ---------
 dstat_interface/core/interface/adc_pot.py     | 139 ---
 .../core/interface/analysis_options.glade     | 159 ----
 dstat_interface/core/interface/calib.glade    | 363 --------
 .../core/interface/chronoamp.glade            | 186 ----
 dstat_interface/core/interface/cv.glade       | 362 --------
 dstat_interface/core/interface/data_view.py   |  81 --
 dstat_interface/core/interface/db.glade       | 204 -----
 dstat_interface/core/interface/db.py          | 143 ---
 dstat_interface/core/interface/dpv.glade      | 404 ---------
 .../core/interface/dstatinterface.glade       | 737 ---------------
 dstat_interface/core/interface/exp_int.py     | 540 -----------
 dstat_interface/core/interface/exp_window.py  | 129 ---
 dstat_interface/core/interface/hw_info.py     | 110 ---
 dstat_interface/core/interface/lsv.glade      | 307 -------
 dstat_interface/core/interface/pd.glade       | 348 -------
 dstat_interface/core/interface/plot.py        |  94 --
 dstat_interface/core/interface/plot_ui.py     |  33 -
 dstat_interface/core/interface/potexp.glade   |  97 --
 dstat_interface/core/interface/save.py        | 226 -----
 dstat_interface/core/interface/swv.glade      | 433 ---------
 dstat_interface/core/microdrop.py             |  90 --
 dstat_interface/core/params.py                |  82 --
 dstat_interface/core/plugin.py                | 161 ----
 dstat_interface/core/state.py                 |  10 +
 dstat_interface/core/tasks.py                 |  35 +
 dstat_interface/core/utils/__init__.py        |   0
 dstat_interface/core/utils/version.py         | 137 ---
 dstat_interface/main.py                       | 854 ------------------
 dstat_interface/utils.py                      |  25 +
 pavement.py                                   |  31 -
 poetry.lock                                   | 199 ++++
 pyproject.toml                                |  27 +
 version.py                                    | 137 ---
 66 files changed, 2211 insertions(+), 10236 deletions(-)
 delete mode 100644 MANIFEST.in
 delete mode 100644 conda-env.yml
 delete mode 100755 dstat_interface/core/analysis.py
 create mode 100644 dstat_interface/core/daemon.py
 create mode 100644 dstat_interface/core/dstat-cli.py
 delete mode 100644 dstat_interface/core/dstat-interface.bat
 delete mode 100644 dstat_interface/core/dstat.spec
 delete mode 100755 dstat_interface/core/dstat/dfu.py
 create mode 100644 dstat_interface/core/dstat/experiment_handler.py
 create mode 100644 dstat_interface/core/dstat/experiment_process.py
 create mode 100644 dstat_interface/core/dstat/simulator.py
 create mode 100644 dstat_interface/core/dstat/test/test_comm.py
 create mode 100644 dstat_interface/core/dstat/utility.py
 delete mode 100755 dstat_interface/core/errors.py
 delete mode 100644 dstat_interface/core/experiments/__init__.py
 delete mode 100755 dstat_interface/core/experiments/cal.py
 delete mode 100644 dstat_interface/core/experiments/cv.py
 create mode 100644 dstat_interface/core/experiments/experiment_container.py
 delete mode 100644 dstat_interface/core/experiments/experiment_loops.py
 delete mode 100755 dstat_interface/core/experiments/experiment_template.py
 delete mode 100644 dstat_interface/core/experiments/idle.py
 delete mode 100644 dstat_interface/core/experiments/pot.py
 delete mode 100644 dstat_interface/core/interface/__init__.py
 delete mode 100644 dstat_interface/core/interface/acv.glade
 delete mode 100644 dstat_interface/core/interface/adc_pot.glade
 delete mode 100755 dstat_interface/core/interface/adc_pot.py
 delete mode 100644 dstat_interface/core/interface/analysis_options.glade
 delete mode 100644 dstat_interface/core/interface/calib.glade
 delete mode 100644 dstat_interface/core/interface/chronoamp.glade
 delete mode 100644 dstat_interface/core/interface/cv.glade
 delete mode 100644 dstat_interface/core/interface/data_view.py
 delete mode 100644 dstat_interface/core/interface/db.glade
 delete mode 100755 dstat_interface/core/interface/db.py
 delete mode 100644 dstat_interface/core/interface/dpv.glade
 delete mode 100644 dstat_interface/core/interface/dstatinterface.glade
 delete mode 100755 dstat_interface/core/interface/exp_int.py
 delete mode 100755 dstat_interface/core/interface/exp_window.py
 delete mode 100755 dstat_interface/core/interface/hw_info.py
 delete mode 100644 dstat_interface/core/interface/lsv.glade
 delete mode 100644 dstat_interface/core/interface/pd.glade
 delete mode 100755 dstat_interface/core/interface/plot.py
 delete mode 100644 dstat_interface/core/interface/plot_ui.py
 delete mode 100644 dstat_interface/core/interface/potexp.glade
 delete mode 100755 dstat_interface/core/interface/save.py
 delete mode 100644 dstat_interface/core/interface/swv.glade
 delete mode 100644 dstat_interface/core/microdrop.py
 delete mode 100755 dstat_interface/core/params.py
 delete mode 100644 dstat_interface/core/plugin.py
 create mode 100644 dstat_interface/core/state.py
 create mode 100644 dstat_interface/core/tasks.py
 delete mode 100644 dstat_interface/core/utils/__init__.py
 delete mode 100644 dstat_interface/core/utils/version.py
 delete mode 100755 dstat_interface/main.py
 create mode 100644 dstat_interface/utils.py
 delete mode 100644 pavement.py
 create mode 100644 poetry.lock
 create mode 100644 pyproject.toml
 delete mode 100644 version.py

diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 711422b..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,14 +0,0 @@
-include RELEASE-VERSION
-include version.py
-include setup.py
-include main.py
-include paver-minilib.zip
-include LICENSE
-include CHANGELOG
-include README.markdown
-include core/utils/RELEASE-VERSION
-recursive-include dstat_interface *
-recursive-exclude dstat_interface *.pyc
-recursive-exclude dstat_interface *~
-recursive_exclude core last_params.yml
-recursive-exclude . .DS_Store
\ No newline at end of file
diff --git a/conda-env.yml b/conda-env.yml
deleted file mode 100644
index 8bce2bc..0000000
--- a/conda-env.yml
+++ /dev/null
@@ -1,137 +0,0 @@
-name: dstat
-channels:
-  - conda-forge
-  - mdryden
-  - defaults
-dependencies:
-  - ca-certificates=2018.4.16=0
-  - certifi=2018.4.16=py27_0
-  - nb_conda_kernels=2.1.0=py27_0
-  - openssl=1.0.2o=0
-  - appnope=0.1.0=py27_0
-  - backports=1.0=py27_0
-  - backports_abc=0.5=py27_0
-  - bleach=1.5.0=py27_0
-  - configparser=3.5.0=py27_0
-  - curl=7.54.1=0
-  - cycler=0.10.0=py27_0
-  - decorator=4.1.2=py27_0
-  - entrypoints=0.2.3=py27_0
-  - enum34=1.1.6=py27_0
-  - expat=2.1.0=0
-  - freetype=2.5.5=2
-  - funcsigs=1.0.2=py27hb9f6266_0
-  - functools32=3.2.3.2=py27_0
-  - get_terminal_size=1.0.0=py27_0
-  - gettext=0.19.8=1
-  - git=2.11.1=0
-  - html5lib=0.9999999=py27_0
-  - icu=54.1=0
-  - intel-openmp=2018.0.0=h8158457_8
-  - ipykernel=4.6.1=py27_0
-  - ipython=5.3.0=py27_0
-  - ipython-notebook=4.0.4=py27_0
-  - ipython_genutils=0.2.0=py27_0
-  - jbig=2.1=0
-  - jinja2=2.9.6=py27_0
-  - jpeg=9b=0
-  - jsonschema=2.6.0=py27_0
-  - jupyter_client=5.1.0=py27_0
-  - jupyter_core=4.3.0=py27_0
-  - krb5=1.13.2=0
-  - libcxx=4.0.1=h579ed51_0
-  - libcxxabi=4.0.1=hebd6815_0
-  - libffi=3.2.1=1
-  - libgfortran=3.0.1=h93005f0_2
-  - libiconv=1.14=0
-  - libpng=1.6.30=1
-  - libssh2=1.8.0=0
-  - libtiff=4.0.6=3
-  - llvmlite=0.21.0=py27hac8ee23_0
-  - markupsafe=1.0=py27_0
-  - matplotlib=2.0.2=np113py27_0
-  - mistune=0.7.4=py27_0
-  - mkl=2018.0.1=hfbd8650_4
-  - nbconvert=5.2.1=py27_0
-  - nbformat=4.4.0=py27_0
-  - notebook=5.0.0=py27_0
-  - numba=0.36.2=np113py27h7c931aa_0
-  - numpy=1.13.3=py27h62f9060_0
-  - pandas=0.20.3=py27_0
-  - pandocfilters=1.4.2=py27_0
-  - path.py=10.3.1=py27_0
-  - pathlib2=2.3.0=py27_0
-  - patsy=0.4.1=py27_0
-  - pcre=8.39=1
-  - pexpect=4.2.1=py27_0
-  - pickleshare=0.7.4=py27_0
-  - pip=9.0.1=py27_1
-  - prompt_toolkit=1.0.15=py27_0
-  - ptyprocess=0.5.2=py27_0
-  - pygments=2.2.0=py27_0
-  - pyparsing=2.2.0=py27_0
-  - pyqt=5.6.0=py27_2
-  - python=2.7.13=0
-  - python-dateutil=2.6.1=py27_0
-  - pytz=2017.2=py27_0
-  - pyyaml=3.12=py27_0
-  - pyzmq=16.0.2=py27_0
-  - qt=5.6.2=2
-  - readline=6.2=2
-  - scandir=1.5=py27_0
-  - scipy=1.0.0=py27h793f721_0
-  - seaborn=0.8=py27_0
-  - setuptools=36.4.0=py27_0
-  - simplegeneric=0.8.1=py27_1
-  - singledispatch=3.4.0.3=py27_0
-  - sip=4.18=py27_0
-  - six=1.10.0=py27_0
-  - sqlite=3.13.0=0
-  - ssl_match_hostname=3.5.0.1=py27_0
-  - statsmodels=0.8.0=np113py27_0
-  - subprocess32=3.2.7=py27_0
-  - terminado=0.6=py27_0
-  - testpath=0.3.1=py27_0
-  - tk=8.5.18=0
-  - tornado=4.5.2=py27_0
-  - traitlets=4.3.2=py27_0
-  - wcwidth=0.1.7=py27_0
-  - wheel=0.29.0=py27_0
-  - xz=5.2.3=0
-  - yaml=0.1.6=0
-  - zlib=1.2.11=0
-  - adwaita-icon-theme=3.24.0=1
-  - arrow=0.10.0=py27_0
-  - at-spi2-atk=2.24.1=2
-  - at-spi2-core=2.24.1=2
-  - atk=2.24.0=3
-  - cairo-gobject=1.14.8=8
-  - dbus-client=1.10.18=0
-  - dfu-programmer=0.7.2=2
-  - dstat-interface=1.4.6=py27_0
-  - dstat-interface-deps=1.0=0
-  - gdk-pixbuf=2.36.6=2
-  - glib=2.52.2=5
-  - gobject-introspection=1.52.1=2
-  - gtk3=3.22.15=4
-  - harfbuzz=1.4.6=3
-  - libepoxy=1.4.2=5
-  - libusb=1.0.21=0
-  - pango=1.40.6=2
-  - pixman=0.34.0=1
-  - py2cairo=1.10.0=py27_0
-  - pygobject3=3.24.2=py27_3
-  - pyserial=3.3=py27_0
-  - zmq-plugin=0.2.post14=py27_0
-  - pip:
-    - backports.shutil-get-terminal-size==1.0.0
-    - backports.shutil-which==3.5.1
-    - backports.ssl-match-hostname==3.5.0.1
-    - chardet==3.0.4
-    - colorama==0.3.9
-    - idna==2.6
-    - paver==1.2.4
-    - pygobject==3.24.1
-    - requests==2.18.4
-    - urllib3==1.22
-    - vmprof==0.4.10
diff --git a/dstat_interface/__init__.py b/dstat_interface/__init__.py
index e69de29..6206dbf 100644
--- a/dstat_interface/__init__.py
+++ b/dstat_interface/__init__.py
@@ -0,0 +1,4 @@
+from pathlib import Path
+from single_source import get_version
+
+__version__ = get_version(__name__, Path(__file__).parent.parent)
diff --git a/dstat_interface/core/analysis.py b/dstat_interface/core/analysis.py
deleted file mode 100755
index 747df18..0000000
--- a/dstat_interface/core/analysis.py
+++ /dev/null
@@ -1,194 +0,0 @@
-#!/usr/bin/env python
-#     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/>.
-
-"""
-Functions for analyzing data.
-""" 
-import logging
-import os
-
-from numpy import mean, trapz
-
-logger = logging.getLogger(__name__)
-mod_dir = os.path.dirname(os.path.abspath(__file__))
-
-class AnalysisOptions(object):
-    """Analysis options window."""
-    def __init__(self, builder):
-        self.builder = builder
-        self.builder.add_from_file(
-            os.path.join(mod_dir, 'interface/analysis_options.glade'))
-        self.builder.connect_signals(self)
-        
-        self.window = self.builder.get_object('analysis_dialog')
-        self.stats_button = self.builder.get_object('stats_button')
-        self.stats_start = self.builder.get_object('stats_start_spin')
-        self.stats_start_button = self.builder.get_object('stats_start_button')
-        self.stats_stop = self.builder.get_object('stats_stop_spin')        
-        self.stats_stop_button = self.builder.get_object('stats_stop_button')
-
-        self.stats_button.connect('toggled',
-                                  self.on_button_toggled_hide,
-                                      [self.stats_stop, 
-                                       self.stats_stop_button,
-                                       self.stats_start,
-                                       self.stats_start_button
-                                       ]
-                                  )
-        
-        self._params = {'stats_true':False,
-                        'stats_start_true':False,
-                        'stats_stop':0,
-                        'stats_stop_true':False,
-                        'stats_start':0
-                        }
-    
-    def show(self):
-        """Show options window."""
-        self.window.run()
-        self.window.hide()
-    
-    def on_button_toggled_hide(self, control, widgets):
-        """Hide unchecked fields"""
-        active = control.get_active()
-        
-        for widget in widgets:
-            widget.set_sensitive(active)
-    
-    @property
-    def params(self):
-        """Getter for analysis params"""
-        self._params['stats_true'] = self.stats_button.get_active()
-        
-        self._params['stats_start_true'] = self.stats_start_button.get_active()
-        self._params['stats_start'] = self.stats_start.get_value()
-        
-        self._params['stats_stop_true'] = self.stats_stop_button.get_active()
-        self._params['stats_stop'] = self.stats_stop.get_value()
-
-        return self._params
-    
-    @params.setter
-    def params(self, params):
-        for key in self._params:
-            if key in params:
-                self._params[key] = params[key]
-        
-        self.stats_button.set_active(self._params['stats_true'])
-        self.stats_start_button.set_active(self._params['stats_start_true'])
-        self.stats_start.set_value(self._params['stats_start'])
-        self.stats_stop_button.set_active(self._params['stats_stop_true'])
-        self.stats_stop.set_value(self._params['stats_stop'])
-
-def do_analysis(experiment):
-    """Takes an experiment class instance and runs selected analysis."""
-    
-    experiment.analysis = {}
-    
-    if experiment.parameters['stats_true']:
-        if (experiment.parameters['stats_start_true'] or
-            experiment.parameters['stats_stop_true']):
-            
-            if experiment.parameters['stats_start_true']:
-                start = experiment.parameters['stats_start']
-            else:
-                start = min(experiment.data['data'][0][0])
-        
-            if experiment.parameters['stats_stop_true']:
-                stop = experiment.parameters['stats_stop']
-            else:
-                stop = min(experiment.data['data'][0][0])
-                
-            data = _data_slice(experiment.data['data'],
-                               start,
-                               stop
-                               )
-        else:
-            data = experiment.data['data']
-        
-        experiment.analysis.update(_summary_stats(data))
-    
-    try:
-        x, y = experiment.data['ft'][0]
-        experiment.analysis['FT Integral'] = _integrateSpectrum(
-                x,
-                y,
-                float(experiment.parameters['sync_freq']),
-                float(experiment.parameters['fft_int'])
-        )
-
-    except KeyError:
-        pass
-
-def _data_slice(data, start, stop):
-    """Accepts data (as list of tuples of lists) and returns copy of data
-    between start and stop (in whatever x-axis units for the experiment type).
-    """
-    output = []
-    
-    for scan in range(len(data)):
-        t = []
-        for i in range(len(data[scan])):
-            t.append([])
-        output.append(tuple(t))
-        
-        for i in range(len(data[scan][0])): # x-axis column
-            if data[scan][0][i] >= start or data[scan][0][i] <= stop:
-                for d in range(len(output[scan])):
-                    output[scan][d].append(data[scan][d][i])              
-    
-    return output
-                    
-def _summary_stats(data):
-    """Takes data and returns summary statistics of first y variable as dict of 
-    name, (scan, values).
-    """
-    
-    stats = {'min':[],'max':[], 'mean':[]}
-    
-    for scan in range(len(data)):
-        stats['min'].append(
-                            (scan, min(data[scan][1]))
-                            )
-        stats['max'].append(
-                            (scan, max(data[scan][1]))
-                            )
-        stats['mean'].append(
-                            (scan, mean(data[scan][1]))
-                            )
-    return stats
-    
-def _integrateSpectrum(x, y, target, bandwidth):
-    """
-    Returns integral of range of bandwidth centered on target.
-    """
-    j = 0
-    k = len(x)
-
-    for i in range(len(x)):
-        if x[i] >= target-bandwidth/2:
-            j = i
-            break
-    
-    for i in range(j,len(x)):
-        if x[i] >= target+bandwidth/2:
-            k = i
-            break
-
-    return [(0, trapz(y=y[j:k], x=x[j:k]))]
\ No newline at end of file
diff --git a/dstat_interface/core/daemon.py b/dstat_interface/core/daemon.py
new file mode 100644
index 0000000..9703c88
--- /dev/null
+++ b/dstat_interface/core/daemon.py
@@ -0,0 +1,154 @@
+import logging
+import pickle
+from math import ceil
+
+import pynng
+import trio
+
+from dstat_interface.core.dstat.comm import SerialDevices, dstat_connect
+from dstat_interface.core.dstat.comm import state as dstat_state
+from dstat_interface.core.experiments.experiment_container import ExperimentContainer
+from dstat_interface.core.experiments.lsv import LSVExperimentContainer, CVExperimentContainer
+from dstat_interface.core.experiments.swv import SWVExperimentContainer, DPVExperimentContainer
+from dstat_interface.core.experiments.chronoamp import CAExperimentContainer
+from dstat_interface.core.tasks import ExperimentBaseTasks
+from utils import runtime_dir
+
+logger = logging.getLogger(__name__)
+
+e = [LSVExperimentContainer, CVExperimentContainer, SWVExperimentContainer, DPVExperimentContainer, CAExperimentContainer]
+experiments: dict[str, type[ExperimentContainer]] = {exp.experiment_id: exp for exp in e}
+
+
+class DaemonExperimentTasks(ExperimentBaseTasks):
+    def __init__(self, exp_con: ExperimentContainer, socket: pynng.Pub0, socket_int: pynng.Bus0):
+        super().__init__(exp_con)
+        self.socket = socket
+        self.socket_int = socket_int
+        self.data_index = 0
+        self.tasks += [self.send_data, self.check_interrupts]
+
+    async def update_progress(self, cancel_scope: trio.CancelScope):
+        while True:
+            scan = self.exp_con.progress_scan
+            last_progress = 0
+
+            while True:
+                await trio.sleep(0.2)
+                try:
+                    progress = ceil(self.exp_con.get_progress())
+
+                # End of Scan
+                except StopIteration:
+                    await self.socket.asend(f'progress: {scan}, 100'.encode('utf-8'))
+                    break
+
+                if progress > last_progress:
+                    await self.socket.asend(f'progress: {scan}, {progress}'.encode('utf-8'))
+                    last_progress = progress
+                if progress >= 100:
+                    break
+
+    async def send_data(self, cancel_scope: trio.CancelScope):
+        while True:
+            await trio.sleep(0.2)
+            new_index = len(self.exp_con.handler_instance.data['timestamp'])
+            if new_index == 0:
+                continue
+            data = {key: value[self.data_index:new_index] for key, value in self.exp_con.handler_instance.data.items()}
+
+            await self.socket.asend(b'data:' + pickle.dumps(data))
+            self.data_index = new_index
+            if self.exp_con.handler_instance.done:
+                cancel_scope.cancel()
+                return
+
+    async def check_interrupts(self, cancel_scope: trio.CancelScope):
+        while True:
+            msg = await self.socket_int.arecv()
+            if msg == b'ABORT':
+                dstat_state.ser.stop_exp()
+
+
+class DStatDaemon(object):
+    def __init__(self, listen_addr_pub=None, listen_addr_ctrl=None, listen_addr_interrupt=None):
+        if listen_addr_pub is None:
+            self.address_pub = 'ipc://' + runtime_dir() + '/dstat-interfaced-pub'
+        if listen_addr_ctrl is None:
+            self.address_ctrl = 'ipc://' + runtime_dir() + '/dstat-interfaced-ctrl'
+        if listen_addr_interrupt is None:
+            self.address_int = 'ipc://' + runtime_dir() + '/dstat-interfaced-int'
+
+        self.socket_pub = pynng.Pub0(listen=self.address_pub)
+        self.socket_ctrl = pynng.Rep0(listen=self.address_ctrl)
+        self.socket_interrupt = pynng.Bus0(listen=self.address_int)
+
+        self.device_list = SerialDevices()
+        self.connected = False
+
+        self.command_map = {'fetch_devices': self.fetch_devices,
+                            'connect_dstat': self.connect_dstat,
+                            'disconnect_dstat': self.disconnect_dstat,
+                            'fetch_experiments': self.fetch_experiments,
+                            'run_experiment': self.run_experiment}
+
+    async def fetch_devices(self, payload: dict = None) -> dict:
+        self.device_list.refresh()
+        return {'ports': self.device_list.ports}
+
+    async def connect_dstat(self, payload: dict = None) -> dict:
+        if not self.connected:
+            self.connected = dstat_connect(payload['port'])
+            msg = f'DStat connected: {self.connected}'
+        else:
+            msg = "DStat already connected"
+        return {'connected': self.connected,
+                'dstat_version': dstat_state.dstat_version,
+                'firmware_version': dstat_state.firmware_version,
+                'board': dstat_state.board_instance,
+                'msg': msg}
+
+    async def disconnect_dstat(self, payload: dict = None):
+        if self.connected:
+            dstat_state.ser.close()
+            self.connected = False
+            return {'connected': False, 'msg': 'DStat disconnected'}
+        else:
+            return {'connected': False, 'msg': 'DStat already disconnected'}
+
+    async def fetch_experiments(self, payload: dict = None):
+        return {'experiments': list(experiments.values())}
+
+    async def run_experiment(self, exp_dict: dict):
+        exp = exp_dict['experiment_id']
+        del exp_dict['experiment_id']
+
+        experiment_container = experiments[exp](exp_dict, mux=dstat_state.board_instance.channels)
+        dstat_state.ser.start_exp(experiment_container.get_proc())
+        experiment_container.start_handler(dstat_state.ser)
+        tasks = DaemonExperimentTasks(experiment_container, self.socket_pub, self.socket_interrupt)
+        await tasks.loop()
+        return {'status': 'done'}
+
+    async def run(self):
+        while True:
+            command: str
+            payload: dict
+            command, payload = pickle.loads(await self.socket_ctrl.arecv())
+            await self.socket_ctrl.asend(pickle.dumps(await (self.command_map[command](payload))))
+
+    def close(self):
+        self.socket_pub.close()
+        self.socket_ctrl.close()
+
+    def __del__(self):
+        self.close()
+
+
+async def main():
+    daemon = DStatDaemon()
+    async with trio.open_nursery() as nursery:
+        nursery.start_soon(daemon.run)
+
+if __name__ == '__main__':
+    trio.run(main)
diff --git a/dstat_interface/core/dstat-cli.py b/dstat_interface/core/dstat-cli.py
new file mode 100644
index 0000000..28e361a
--- /dev/null
+++ b/dstat_interface/core/dstat-cli.py
@@ -0,0 +1,135 @@
+import argparse
+import logging
+import pathlib
+import sys
+from math import ceil
+
+from tqdm import tqdm
+import pandas as pd
+import trio
+
+from dstat_interface.core.tasks import ExperimentBaseTasks
+from . import state
+from .dstat.comm import SerialDevices, dstat_connect, read_settings
+from .experiments.experiment_container import ExperimentContainer
+from .experiments.lsv import LSVExperimentContainer, CVExperimentContainer
+from .experiments.swv import SWVExperimentContainer, DPVExperimentContainer
+from .dstat.state import DStatState
+
+# Setup Logging
+logger = logging.getLogger(__name__)
+
+logging_choices = {'DEBUG': logging.DEBUG,
+                   'INFO': logging.INFO,
+                   'WARNING': logging.WARNING,
+                   'ERROR': logging.ERROR}
+
+experiments = [LSVExperimentContainer, CVExperimentContainer, SWVExperimentContainer, DPVExperimentContainer]
+
+dstat_state = DStatState()
+
+
+class CLIExperimentTasks(ExperimentBaseTasks):
+    async def update_progress(self, cancel_scope):
+        scan = 0
+        while True:
+            if scan == self.exp_con.progress_iters-1:  # Inner loop broken when already at last scan (iters 0-indexed)
+                break
+            scan = self.exp_con.progress_scan
+            last_progress = 0
+            with tqdm(total=100, unit='%', dynamic_ncols=True, desc=f'Scan {scan+1}',
+                      bar_format='{l_bar}{bar}[{elapsed}]') as pbar:
+                while True:
+                    try:
+                        progress = ceil(self.exp_con.get_progress())
+                    except StopIteration:
+                        pbar.update(100 - last_progress)
+                        break
+                    pbar.update(progress-last_progress)
+                    last_progress = progress
+                    if progress >= 100:
+                        break
+                    await trio.sleep(0.2)
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--log-level', choices=logging_choices, default="INFO")
+    parser.add_argument('--experiment-path', type=str, default=None)
+    parser.add_argument('-s', '--simulator', action='store_true')
+    experiment_subparsers = parser.add_subparsers(title="Experiments:", required=True)
+
+    for exp in experiments:
+        experiment = exp({})
+        exp_parser = experiment_subparsers.add_parser(name=experiment.experiment_id)
+        exp_parser.set_defaults(experiment=exp)
+        arguments = experiment.param_input
+        for i in arguments:
+            kind = arguments[i]
+            try:
+                default = experiment.defaults[i]
+            except KeyError:
+                default = None
+            try:
+                doc = experiment.param_docs[i]
+            except KeyError:
+                doc = ''
+
+            if kind is bool:
+                exp_parser.add_argument(f'--{i}', action=f'store_{not experiment.defaults[i]}'.lower(), help=doc)
+            elif isinstance(kind, list):
+                if default is not None:
+                    exp_parser.add_argument(f'--{i}', choices=kind, default=default, help=doc)
+                else:
+                    exp_parser.add_argument(i, metavar=i, choices=kind, help=doc)
+            else:
+                if default is not None:
+                    exp_parser.add_argument(f'--{i}', type=kind, default=default, help=doc)
+                else:
+                    exp_parser.add_argument(i, type=kind, help=doc)
+
+    args = parser.parse_args()
+
+    if args.experiment_path is not None:
+        state.experiment_folder_location = pathlib.Path(args.experiment_path).expanduser() / state.experiment_name
+
+    # Make sure paths exist
+    pathlib.Path(state.experiment_folder_location).mkdir(parents=True, exist_ok=True)
+    pathlib.Path(state.app_dirs.user_config_dir).mkdir(parents=True, exist_ok=True)
+
+    # Logging
+    logfile = state.experiment_folder_location / f'{state.experiment_name}.log'
+    root_logger = logging.getLogger()
+    for handler in logging.root.handlers[:]:
+        logging.root.removeHandler(handler)
+    log_handlers = [logging.StreamHandler(), logging.FileHandler(logfile)]
+    log_formatter = logging.Formatter(
+        fmt='%(asctime)s %(levelname)s: [%(name)s] %(message)s',
+        datefmt='%H:%M:%S',
+    )
+
+    for handler in log_handlers:
+        handler.setFormatter(log_formatter)
+        root_logger.addHandler(handler)
+    root_logger.setLevel(logging_choices[args.log_level])
+
+    if args.simulator:
+        dstat_connect('simulator')
+    else:
+        ports = SerialDevices()
+        dstat_connect(ports.ports[0])
+
+    logger.info('DStat ver: %s Firmware ver: %s', dstat_state.dstat_version, dstat_state.firmware_version)
+    read_settings()
+    exp: ExperimentContainer = args.experiment(vars(args))  # Convert namespace to dict
+
+    dstat_state.ser.start_exp(exp.get_proc())
+
+    exp.start_handler(dstat_state.ser)
+    tasks = CLIExperimentTasks(exp)
+
+    trio.run(tasks.loop)
+    data = pd.DataFrame(exp.handler_instance.data)
+    data.iloc[1:].to_csv(state.experiment_folder_location / 'data.csv')
+    dstat_state.ser.close()
+    sys.exit()
diff --git a/dstat_interface/core/dstat-interface.bat b/dstat_interface/core/dstat-interface.bat
deleted file mode 100644
index da83e27..0000000
--- a/dstat_interface/core/dstat-interface.bat
+++ /dev/null
@@ -1 +0,0 @@
-python .\main.py
diff --git a/dstat_interface/core/dstat.spec b/dstat_interface/core/dstat.spec
deleted file mode 100644
index c0f001d..0000000
--- a/dstat_interface/core/dstat.spec
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- 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/dstat_interface/core/dstat/__init__.py b/dstat_interface/core/dstat/__init__.py
index 1fb1ae9..e69de29 100644
--- a/dstat_interface/core/dstat/__init__.py
+++ b/dstat_interface/core/dstat/__init__.py
@@ -1,3 +0,0 @@
-from . import comm
-from . import dfu
-from . import state
\ No newline at end of file
diff --git a/dstat_interface/core/dstat/boards.py b/dstat_interface/core/dstat/boards.py
index da4927f..31c0581 100755
--- a/dstat_interface/core/dstat/boards.py
+++ b/dstat_interface/core/dstat/boards.py
@@ -22,20 +22,51 @@ from __future__ import division, absolute_import, print_function, unicode_litera
 import sys
 import inspect
 import logging
+from abc import ABCMeta, abstractmethod
+from typing import Union, Type
 
 from pkg_resources import parse_version, parse_requirements
 
 logger = logging.getLogger(__name__)
 
 
-class BaseBoard(object):
+class BaseBoard(object, metaclass=ABCMeta):
     pcb_version = 'x.x.x'
     booster = False
+    mux = False
+    channels = 1
 
     def __init__(self):
         self.max_freq = 5000
         self.max_scans = 255
         self.max_time = 65535
+        self.mv_max = 1500
+        self.mv_min = -1500
+
+        self.gain = [1, 1e2, 3e3, 3e4, 3e5, 3e6, 3e7, 1e8]
+        self.gain_labels = ["Bypass", "100 Ω (15 mA FS)", "3 kΩ (500 µA FS)",
+                            "30 kΩ (50 µA FS)", "300 kΩ (5 µA FS)",
+                            "3 MΩ (500 nA FS)", "30 MΩ (50 nA FS)",
+                            "100 MΩ (15 nA FS)"
+                           ]
+
+        self.adc_rate_labels = ['2.5', '5', '10', '15', '25', '30', '50', '60', '100', '500', '1000', '2000', '3750',
+                                '7500', '15000', '30000']
+
+        self.adc_rate_codes = [3, 19, 35, 51, 67, 83, 99, 114, 130, 146, 161, 176, 192, 208, 224, 240]
+        self.adc_rate_default_index = 7
+
+        self.adc_pga = [1, 2, 4, 8, 16, 32, 64]
+        self.adc_pga_labels = [f'{pga}X' for pga in self.adc_pga]
+        self.adc_pga_codes = list(range(7))
+        self.adc_pga_default_index = 1
+
+        self.gain_trim = [None, 'r100_trim', 'r3k_trim',
+                          'r30k_trim', 'r300k_trim', 'r3M_trim',
+                          'r30M_trim', 'r100M_trim']
+        self.gain_settings = len(self.gain)
+        self.gain_default_index = 2
+        self.re_voltage_scale = 1
 
         self.setup()
         assert len(self.gain) == self.gain_settings
@@ -43,6 +74,7 @@ class BaseBoard(object):
         if self.gain_trim is not None:
             assert len(self.gain_trim) == self.gain_settings
 
+    @abstractmethod
     def setup(self):
         """Override in subclasses to provide correct numbers"""
         self.gain = [1, 1e2, 3e3, 3e4, 3e5, 3e6, 3e7, 1e8]
@@ -114,6 +146,11 @@ class V1_2Board(BaseBoard):
         self.re_voltage_scale = 1
 
 
+class V1_2BoardMUX(V1_2Board):
+    mux = True
+    channels = 16
+
+
 def __get_all_subclasses(cls):
     all_subclasses = []
 
@@ -124,13 +161,13 @@ def __get_all_subclasses(cls):
     return all_subclasses
 
 
-def find_board(version, booster=False):
+def find_board(version, booster=False, mux=False) -> Union[None, Type[BaseBoard]]:
     """Returns highest compatible board class or None if none available."""
     boards = __get_all_subclasses(BaseBoard)
     candidates = []
     for board in boards:
-        req = parse_requirements('dstat~={}'.format(board.pcb_version)).next()
-        if board.booster == booster and version in req:
+        req = next(parse_requirements('dstat~={}'.format(board.pcb_version)))
+        if board.booster == booster and board.mux == mux and version in req:
             candidates.append(board)
     try:
         picked = sorted(candidates,
diff --git a/dstat_interface/core/dstat/comm.py b/dstat_interface/core/dstat/comm.py
index d78ea58..916a8fd 100755
--- a/dstat_interface/core/dstat/comm.py
+++ b/dstat_interface/core/dstat/comm.py
@@ -17,119 +17,108 @@
 #     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 time
-import struct
-import multiprocessing as mp
-from collections import OrderedDict
 import logging
+import multiprocessing as mp
+import time
+from datetime import datetime
+from typing import Union, Tuple
 
-from pkg_resources import parse_version
-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)
 import serial
+from pkg_resources import parse_version
 from serial.tools import list_ports
 
-from ..errors import InputError, VarError
+from dstat_interface.core.dstat import boards
+from dstat_interface.core.dstat.state import DStatState
+from .utility import SettingsProcess, VersionCheckProcess
+from .experiment_process import BaseExperimentProcess
+from .simulator import SerialSim
 
 logger = logging.getLogger(__name__)
-dstat_logger = logging.getLogger("{}.DSTAT".format(__name__))
-exp_logger = logging.getLogger("{}.Experiment".format(__name__))
+dstat_logger = logging.getLogger(f'{__name__}.DSTAT')
+exp_logger = logging.getLogger(f'{__name__}.Experiment')
+
+state = DStatState()
 
-from . import state
 
 class AlreadyConnectedError(Exception):
     def __init__(self):
-        super(AlreadyConnectedError, self).__init__(self,
-            "Serial instance already connected.")
+        super().__init__('Serial instance already connected.')
+
 
 class NotConnectedError(Exception):
     def __init__(self):
-        super(NotConnectedError, self).__init__(self,
-            "Serial instance not connected.")
-            
-class ConnectionError(Exception):
-    def __init__(self):
-        super(ConnectionError, self).__init__(self,
-            "Could not connect.")
+        super().__init__('Serial instance not connected.')
+
 
 class TransmitError(Exception):
     def __init__(self):
-        super(TransmitError, self).__init__(self,
-            "No reply received.")
+        super().__init__('No reply received.')
 
 
-def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe):
+def _serial_process(ser_port: str, proc_pipe: mp.connection.Connection, ctrl_pipe: mp.connection.Connection,
+                    data_pipe: mp.connection.Connection, poll_s: float = .1):
     ser_logger = logging.getLogger("{}._serial_process".format(__name__))
-    connected = False
+    exc = None
 
     for i in range(5):
-        time.sleep(1) # Give OS time to enumerate
-    
+        time.sleep(1)  # Give OS time to enumerate
+
         try:
-            ser = serial.Serial(ser_port, timeout=1)
-            # ser = serial.Serial(ser_port, timeout=1)
-            ser_logger.info("Connecting")
-            time.sleep(.5)
-            connected = True
-        except serial.SerialException:
-            pass
-        
-        if connected is True:
+            if ser_port == 'simulator':
+                ser = SerialSim(ser_port, timeout=1)
+            else:
+                ser = serial.Serial(ser_port, timeout=1)
+            ser_logger.info('Connecting')
             break
-    
-    try:
-        if ser.isOpen() is False:
-            ser_logger.info("Connection Error")
-            proc_pipe.send("SERIAL_ERROR")
-            return 1
-    except UnboundLocalError:  # ser doesn't exist
-        ser_logger.info("Connection Error")
-        proc_pipe.send("SERIAL_ERROR")
-        return 1
-        
-    ser.write('!0 ')
-    
+        except serial.SerialException as e:
+            exc = e
+    else:
+        if exc:
+            raise exc
+        else:
+            raise serial.SerialException(f'Could not connect on {ser_port}')
+
+    ser.write(b'!0 ')
+
     for i in range(10):
-        if ser.readline().rstrip()=="@ACK 0":
-            if ser.readline().rstrip()=="@RCV 0":
+        if ser.readline().rstrip() == b'@ACK 0':
+            if ser.readline().rstrip() == b'@RCV 0':
                 break
         else:
             time.sleep(.5)
             ser.reset_input_buffer()
-            ser.write('!0 ')
-            time.sleep(.1)
+            ser.write(b'!0 ')
+            time.sleep(poll_s)
+    else:
+        raise TransmitError()
 
     while True:
-        # These can only be called when no experiment is running
-        if ctrl_pipe.poll(): 
-            ctrl_buffer = ctrl_pipe.recv()
+        # These can only be called when no experiment is running, otherwise ctrl_pipe passes to proc
+        if ctrl_pipe.poll():
+            ctrl_buffer: str = ctrl_pipe.recv()
             if ctrl_buffer in ('a', "DISCONNECT"):
                 proc_pipe.send("ABORT")
                 try:
-                    ser.write('a')
+                    ser.write(b'a')
                 except serial.SerialException:
                     return 0
                 ser_logger.info("ABORT")
-                
+
                 if ctrl_buffer == "DISCONNECT":
                     ser_logger.info("DISCONNECT")
                     ser.rts = False
                     ser._update_dtr_state()  # Need DTR update on Windows
-                    
                     ser.close()
                     proc_pipe.send("DISCONNECT")
                     return 0
             else:
-                ser.write(ctrl_buffer)
-            
+                ser.write(ctrl_buffer.encode('ascii'))
+
         elif proc_pipe.poll():
+            # Flush ctrl_pipe
             while ctrl_pipe.poll():
                 ctrl_pipe.recv()
+
             try:
                 return_code = proc_pipe.recv().run(ser, ctrl_pipe, data_pipe)
             except serial.SerialException:
@@ -141,59 +130,49 @@ def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe):
             ser_logger.info('Return code: %s', str(return_code))
 
             proc_pipe.send(return_code)
-        
+
         else:
-            time.sleep(.1)
-            
+            time.sleep(poll_s)
 
 
-class SerialConnection(GObject.Object):
-    __gsignals__ = {
-        'connected': (GObject.SIGNAL_RUN_FIRST, None, ()),
-        'disconnected': (GObject.SIGNAL_RUN_FIRST, None, ())
-    }
-    
+class SerialConnection(object):
     def __init__(self):
-        super(SerialConnection, self).__init__()
         self.connected = False
-    
-    def connect(self, ser_port):
+        self.proc_pipe_p, self.proc_pipe_c = mp.Pipe(duplex=True)
+        self.ctrl_pipe_p, self.ctrl_pipe_c = mp.Pipe(duplex=True)
+        self.data_pipe_p, self.data_pipe_c = mp.Pipe(duplex=True)
+        self.proc: Union[None, mp.Process] = None
+
+    def open(self, ser_port: str):
         if self.connected is False:
             self.proc_pipe_p, self.proc_pipe_c = mp.Pipe(duplex=True)
             self.ctrl_pipe_p, self.ctrl_pipe_c = mp.Pipe(duplex=True)
             self.data_pipe_p, self.data_pipe_c = mp.Pipe(duplex=True)
-    
+
             self.proc = mp.Process(target=_serial_process, args=(ser_port,
-                                    self.proc_pipe_c, self.ctrl_pipe_c,
-                                    self.data_pipe_c))
+                                                                 self.proc_pipe_c, self.ctrl_pipe_c, self.data_pipe_c))
             self.proc.start()
-            time.sleep(2)
             if self.proc.is_alive() is False:
                 raise ConnectionError()
-                return False
             self.connected = True
-            self.emit('connected')
             return True
         else:
             raise AlreadyConnectedError()
-            return False
-    
+
     def assert_connected(self):
         if self.connected is False:
             raise NotConnectedError()
-    
-    def start_exp(self, exp):
+
+    def start_exp(self, exp: BaseExperimentProcess):
         self.assert_connected()
-        
         self.proc_pipe_p.send(exp)
-    
+
     def stop_exp(self):
         self.send_ctrl('a')
-        
+
     def get_proc(self, block=False):
         self.assert_connected()
-            
-        if block is True:
+        if block:
             return self.proc_pipe_p.recv()
         else:
             if self.proc_pipe_p.poll() is True:
@@ -203,8 +182,7 @@ class SerialConnection(GObject.Object):
 
     def get_ctrl(self, block=False):
         self.assert_connected()
-
-        if block is True:
+        if block:
             return self.ctrl_pipe_p.recv()
         else:
             if self.ctrl_pipe_p.poll() is True:
@@ -212,379 +190,139 @@ class SerialConnection(GObject.Object):
             else:
                 return None
 
-    def get_data(self, block=False):
+    def get_data(self, block=False) -> Union[None, Tuple[datetime, int, bytes]]:
         self.assert_connected()
-        
-        if block is True:
+        if block:
             return self.data_pipe_p.recv()
         else:
-            if self.data_pipe_p.poll() is True:
+            if self.data_pipe_p.poll():
                 return self.data_pipe_p.recv()
             else:
                 return None
-    
+
     def flush_data(self):
         self.assert_connected()
-        
         while self.data_pipe_p.poll() is True:
             self.data_pipe_p.recv()
-    
-    def send_ctrl(self, ctrl):
+
+    def send_ctrl(self, ctrl: str):
         self.assert_connected()
-        
         self.ctrl_pipe_p.send(ctrl)
-    
-    def disconnect(self):
-        logger.info("Disconnecting")
+
+    def close(self):
+        logger.info('Disconnecting')
         self.send_ctrl('DISCONNECT')
         self.proc.join()
-        self.emit('disconnected')
         self.connected = False
 
-class VersionCheck(object):
-    def __init__(self):
-        pass
-        
-    def run(self, ser, ctrl_pipe, data_pipe):
-        """Tries to contact DStat and get version. Returns a tuple of
-        (major, minor). If no response, returns empty tuple.
-            
-        Arguments:
-        ser_port -- address of serial port to use
-        """
-        try:
-            ser.reset_input_buffer()
-            ser.write('!1\n')
-    
-            for i in range(10):
-                if ser.readline().rstrip()=="@ACK 1":
-                    ser.write('V\n')
-                    if ser.readline().rstrip()=="@RCV 1":
-                        break
-                else:
-                    time.sleep(.5)
-                    ser.reset_input_buffer()
-                    ser.write('!1\n')
-                    time.sleep(.1)
-                    
-            for line in ser:
-                dstat_logger.info(line.decode('utf-8'))
-                if line.startswith('V'):
-                    input = line.lstrip('V')
-                elif line.startswith("#"):
-                    dstat_logger.info(line.lstrip().rstrip())
-                elif line.lstrip().startswith("@DONE"):
-                    dstat_logger.debug(line.lstrip().rstrip())
-                    ser.reset_input_buffer()
-                    break
-                    
-            pcb, sep, firmware = input.strip().rpartition('-')
-            
-            if pcb == "":
-                pcb = firmware
-                firmware = False
-                logger.info("Your firmware does not support version detection.")
-                
-                data_pipe.send((pcb, False))
-                
-            else:
-                logger.info(
-                    "Firmware Version: {}".format(
-                        hex(int(firmware)).lstrip('0x')
-                    )
-                )
-                data_pipe.send((
-                                pcb,
-                                hex(int(firmware)).lstrip('0x')
-                                ))
-            
-            logger.info(
-                "PCB Version: {}".format(pcb)
-            )
-
-            status = "DONE"
-        
-        except UnboundLocalError as e:
-            status = "SERIAL_ERROR"
-        except SerialException as e:
-            logger.error('SerialException: %s', e)
-            status = "SERIAL_ERROR"
-        finally:
-            return status
-
-def version_check(ser_port):
+
+def dstat_connect(ser_port) -> bool:
     """Tries to contact DStat and get version. Stores version in state.
     If no response, returns False, otherwise True.
-        
+
     Arguments:
     ser_port -- address of serial port to use
-    """    
+    """
     state.ser = SerialConnection()
-    
-    state.ser.connect(ser_port)
-    state.ser.start_exp(VersionCheck())
+
+    state.ser.open(ser_port)
+    state.ser.start_exp(VersionCheckProcess())
     result = state.ser.get_proc(block=True)
-    if result == "SERIAL_ERROR":
+    if result == 'SERIAL_ERROR':
         state.dstat_version = None
         state.firmware_version = None
         return False
-    else:
-        buffer = state.ser.get_data(block=True)
+
+    cmd, buffer = state.ser.get_data(block=True)
+    if cmd != 'V':
+        logger.error('DStat did not respond correctly')
+        return False
     version, state.firmware_version = buffer
     state.dstat_version = parse_version(version)
-    logger.debug("version_check done")
+
+    cmd, buffer = state.ser.get_data(block=True)
+    if cmd != 'X':
+        logger.error('DStat did not reply to mux channel request')
+        return False
+
+    if buffer > 1:
+        mux = True
+    else:
+        mux = False
+
+    board = boards.find_board(state.dstat_version, mux=mux)
+    if board:
+        state.board_instance = board()
+    logger.debug('version_check done')
     time.sleep(.1)
-    
+
     return True
 
-class Settings(object):
-    def __init__(self, task, settings=None):
-        self.task = task
-        self.settings = settings
-        
-    def run(self, ser, ctrl_pipe, data_pipe):
-        """Tries to contact DStat and get settings. Returns dict of
-        settings.
-        """
-        
-        self.ser = ser
-        
-        if 'w' in self.task:
-            self.write()
-            
-        if 'r' in self.task:
-            data_pipe.send(self.read())
-        
-        status = "DONE"
-        
-        return status
-        
-    def read(self):
-        settings = OrderedDict()
-        self.ser.reset_input_buffer()
-        self.ser.write('!2\n')
-
-        for i in range(10):
-            if self.ser.readline().rstrip()=="@ACK 2":
-                self.ser.write('SR\n')
-                if self.ser.readline().rstrip()=="@RCV 2":
-                    break
-            else:
-                time.sleep(.5)
-                self.ser.reset_input_buffer()
-                self.ser.write('!2\n')
-                time.sleep(.1)
-                
-        for line in self.ser:
-            if line.lstrip().startswith('S'):
-                input = line.lstrip().lstrip('S')
-            elif line.lstrip().startswith("#"):
-                dstat_logger.info(line.lstrip().rstrip())
-            elif line.lstrip().startswith("@DONE"):
-                dstat_logger.debug(line.lstrip().rstrip())
-                self.ser.reset_input_buffer()
-                break
-            
-        parted = input.rstrip().split(':')
-        
-        for i in range(len(parted)):
-            settings[parted[i].split('.')[0]] = [i, parted[i].split('.')[1]]
-
-        return settings
-        
-    def write_command(self, cmd, params=None, retry=5):
-        """Write command to serial with optional number of retries."""
-        def get_reply(retries = 3):
-            while True:
-                reply = self.ser.readline().rstrip()
-                if reply.startswith('#'):
-                    dstat_logger.info(reply)
-                elif reply == "":
-                    retries -= 1
-                    if retries <= 0:
-                        raise TransmitError
-                else:
-                    return reply
-        
-        n = len(cmd)
-        if params is not None:
-            n_params = len(params)
-            
-        for _ in range(retry):
-            tries = 5
-            while True:
-                time.sleep(0.2)
-                self.ser.reset_input_buffer()
-                self.ser.write('!{}\n'.format(n))
-                time.sleep(.1)
-                
-                try:
-                    reply = get_reply()
-                except TransmitError:
-                    if tries <= 0:
-                        continue
-                    tries -= 1
-                    pass
-                else:
-                    break
-        
-            if reply != "@ACK {}".format(n):
-                logger.warning("Expected ACK got: {}".format(reply))
-                continue
-            
-            tries = 5
-            
-            while True:
-                self.ser.write('{}\n'.format(cmd))
-                try:
-                    reply = get_reply()
-                except TransmitError:
-                    if tries <= 0:
-                        continue
-                    tries -= 1
-                    pass
-                else:
-                    break
-        
-            if reply != "@RCV {}".format(n):
-                logger.warning("Expected RCV got: {}".format(reply))
-                continue
-
-            if params is None:
-                return True
-            
-            tries = 5
-            
-            while True:
-                try:
-                    reply = get_reply()
-                except TransmitError:
-                    if tries <= 0:
-                        continue
-                    tries -= 1
-                    pass
-                else:
-                    break
-                
-            if reply != "@RQP {}".format(n_params):
-                logger.warning("Expected RQP got: {}".format(reply))
-                continue
-            
-            tries = 5
-            
-            for i in params:
-                while True:
-                    self.ser.write(i + " ")
-                    try:
-                        reply = get_reply()
-                        if reply == "@RCVC {}".format(i):
-                            break
-                    except TransmitError:
-                        if tries <= 0:
-                            continue
-                        tries -= 1
-                        pass
-                    else:
-                        break
-            return True
-        return False
-        
-    def write(self):
-        write_buffer = range(len(self.settings))
-        
-        for i in self.settings: # make sure settings are in right order
-            write_buffer[self.settings[i][0]] = self.settings[i][1]
-        
-        to_write = " ".join(write_buffer) + " "
-        n = len(to_write)
-        logger.debug("to_write = %s", to_write)
-        
-        if not self.write_command('SW' + to_write):
-            logger.error("Could not write command.")
-        
+
 def read_settings():
     """Tries to contact DStat and get settings. Returns dict of
     settings.
     """
-    
     state.ser.flush_data()
-    state.ser.start_exp(Settings(task='r'))
+    state.ser.start_exp(SettingsProcess(task='r'))
     state.settings = state.ser.get_data(block=True)
 
     logger.info("Read settings from DStat")
     logger.debug("read_settings: %s", state.ser.get_proc(block=True))
-    
-    return
-    
+
+
 def write_settings():
     """Tries to write settings to DStat from global settings var.
     """
-    
     logger.debug("Settings to write: %s", state.settings)
-    
+
     state.ser.flush_data()
-    state.ser.start_exp(Settings(task='w', settings=state.settings))
+    state.ser.start_exp(SettingsProcess(task='w', settings=state.settings))
     logger.info("Wrote settings to DStat")
     logger.debug("write_settings: %s", state.ser.get_proc(block=True))
-    
-    return
-    
-class LightSensor:
-    def __init__(self):
-        pass
-        
-    def run(self, ser, ctrl_pipe, data_pipe):
-        """Tries to contact DStat and get light sensor reading. Returns uint of
-        light sensor clear channel.
-        """
-        
-        ser.reset_input_buffer()
-        ser.write('!')
-                
-        while not ser.read()=="@":
-            self.ser.reset_input_buffer()
-            ser.write('!')
-            
-        ser.write('T')
-        for line in ser:
-            if line.lstrip().startswith('T'):
-                input = line.lstrip().lstrip('T')
-            elif line.lstrip().startswith("#"):
-                dstat_logger.info(line.lstrip().rstrip())
-            elif line.lstrip().startswith("@DONE"):
-                dstat_logger.debug(line.lstrip().rstrip())
-                ser.reset_input_buffer()
-                break
-                
-        parted = input.rstrip().split('.')
-        
-        data_pipe.send(parted[0])
-        status = "DONE"
-        
-        return status
-
-def read_light_sensor():
-    """Tries to contact DStat and get light sensor reading. Returns uint of
-    light sensor clear channel.
-    """
-    
-    state.ser.flush_data()
-    state.ser.start_exp(LightSensor())
-    
-    logger.debug("read_light_sensor: %s", state.ser.get_proc(block=True))
-    
-    return state.ser.get_data(block=True)
+
 
 class SerialDevices(object):
     """Retrieves and stores list of serial devices in self.ports"""
     def __init__(self):
         self.ports = []
         self.refresh()
-    
+
     def refresh(self):
         """Refreshes list of ports."""
         try:
             self.ports, _, _ = zip(*list_ports.grep("DSTAT"))
         except ValueError:
             self.ports = []
-            logger.error("No serial ports found")
\ No newline at end of file
+            logger.error("No serial ports found")
+
+
+if __name__ == '__main__':
+    root_logger = logging.getLogger()
+    for handler in logging.root.handlers[:]:
+        logging.root.removeHandler(handler)
+    log_handlers = [logging.StreamHandler()]
+    log_formatter = logging.Formatter(
+        fmt='%(asctime)u %(levelname)u: [%(name)u] %(message)u',
+        datefmt='%H:%M:%S',
+    )
+    for handler in log_handlers:
+        handler.setFormatter(log_formatter)
+        root_logger.addHandler(handler)
+    root_logger.setLevel(logging.DEBUG)
+
+    ports = SerialDevices()
+
+    while True:
+        try:
+            dstat_connect(ports.ports[0])
+            # dstat_connect('simulator')
+            logger.info('DStat ver: %s Firmware ver: %s', state.dstat_version, state.firmware_version)
+            read_settings()
+            logger.info('Settings:\n\t%s', '\n\t'.join([f'{key}: {value}' for key, value in state.settings.items()]))
+            break
+        except IndexError:
+            logger.info('No DStat Found')
+            time.sleep(5)
+            ports.refresh()
+    state.ser.close()
diff --git a/dstat_interface/core/dstat/dfu.py b/dstat_interface/core/dstat/dfu.py
deleted file mode 100755
index 3b56288..0000000
--- a/dstat_interface/core/dstat/dfu.py
+++ /dev/null
@@ -1,375 +0,0 @@
-#!/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/>.
-
-from __future__ import (absolute_import, division,
-                        print_function, unicode_literals)
-
-import subprocess
-import sys
-import os
-import time
-import logging
-from tempfile import mkdtemp
-from zipfile import ZipFile
-
-if sys.version_info >= (3,):
-    import urllib.request as urllib2
-    import urllib.parse as urlparse
-else:
-    import urllib2
-    import urlparse
-
-logger = logging.getLogger(__name__)
-
-try:
-    import gi
-    gi.require_version('Gtk', '3.0')
-    from gi.repository import Gtk
-except ImportError:
-    print("ERR: GTK not available")
-    sys.exit(1)
-
-import serial
-
-from . import state
-from .comm import dstat_logger, exp_logger
-
-fwurl = "http://microfluidics.utoronto.ca/gitlab/api/v4/projects/4/jobs/artifacts/master/download?job=1.2.3&private_token=zkgSx1FaaTP7yLyFKkX6"
-
-class FWDialog(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, widget=None, data=None):
-        for name, result in assert_deps().items():
-            if result is not True:
-                logger.error("Can't use firmware update module.")
-                self.missing_deps()
-                return
-                
-        self.stop() # Stop OCP
-        version_result, master = test_firmware_version() 
-        
-        if version_result is False:
-            self.git_error()
-            return
-        
-        if version_result == 'latest':
-            message = "Your firmware is already up to date."
-            secondary = "Click yes to reflash firmware anyways."
-        elif version_result == 'devel':
-            message = "Your firmware is not on the master branch."
-            secondary = "You may have a development version. " +\
-                        "Click yes to reflash firmware anyways."
-        elif version_result == 'old':
-            message = "Your firmware is out of date."
-            secondary = "Click yes to flash the latest firmware."
-        
-        dialog = Gtk.MessageDialog(self.parent, 0, Gtk.MessageType.INFO,
-                                   Gtk.ButtonsType.YES_NO, message)
-        dialog.format_secondary_text(secondary)
-        dialog.get_content_area().add(
-            Gtk.Label(
-                label="Installed version: {}".format(state.firmware_version)))
-                
-        dialog.get_content_area().add(
-            Gtk.Label(label="Latest version: {}".format(master)))
-                
-        dialog.show_all()
-        response = dialog.run()
-        
-        if response == Gtk.ResponseType.YES:
-            try:
-                download_fw()
-            except:
-                self.dl_error()
-                return
-            
-            dstat_enter_dfu()
-            
-            self.dfu_notice()
-            self.disconnect()
-            try:
-                dfu_program()
-            except:
-                self.dfu_error()
-                
-            dialog.destroy()
-            
-        else:
-            dialog.destroy()
-    
-    def missing_deps(self):
-        dialog = Gtk.MessageDialog(
-                     self.parent, 0, Gtk.MessageType.ERROR,
-                     Gtk.ButtonsType.OK, "Missing Dependencies")
-        
-        dialog.format_secondary_text('Check console for more info.')
-        
-        dialog.connect('response', self.destroy)
-        dialog.show()
-    
-    def git_error(self):
-        dialog = Gtk.MessageDialog(
-                     self.parent, 0, Gtk.MessageType.ERROR,
-                     Gtk.ButtonsType.OK, "Git Error")
-        
-        dialog.format_secondary_text('Check console for more info.')
-        
-        dialog.connect('response', self.destroy)
-        dialog.show()
-        
-    def dl_error(self):
-        dialog = Gtk.MessageDialog(
-                     self.parent, 0, Gtk.MessageType.ERROR,
-                     Gtk.ButtonsType.OK, "Download Error")
-        
-        dialog.format_secondary_text('Check console for more info.')
-        
-        dialog.connect('response', self.destroy)
-        dialog.show()
-    
-    def dfu_notice(self):
-        dialog = Gtk.MessageDialog(
-                     self.parent, 0, Gtk.MessageType.INFO,
-                     Gtk.ButtonsType.OK, "Note about DFU")
-        
-        dialog.format_secondary_text("Click OK once the DStat has connected in "
-            + "DFU mode. Windows doesn't seem to like the automatic reboot. " 
-            + "Try holding down the reset button while plugging the "
-            + 'USB port in (No LEDs should be lit), then click OK. Make sure '
-            + 'the DFU driver from the dfu-programmer directory is installed.')
-        
-        dialog.run()
-        dialog.destroy()
-        
-    def dfu_error(self):
-        dialog = Gtk.MessageDialog(
-                     self.parent, 0, Gtk.MessageType.ERROR,
-                     Gtk.ButtonsType.OK, "Could not update over DFU")
-        
-        dialog.format_secondary_text('Check console for more info.')
-        
-        dialog.connect('response', self.destroy)
-        dialog.show()
-        
-    def destroy(self, widget=None, data=None):
-        widget.destroy()
-    
-
-def assert_deps():
-    deps = {'git' : 'git --version',
-            'dfu-programmer' : 'dfu-programmer --version'}
-    
-    result = {}
-    
-    for key, command in deps.items():        
-        try:
-            output = subprocess.check_output(command.split(),
-                                             stderr=subprocess.STDOUT)
-            logger.info("%s\n%s", command, output)
-            result[key] = True
-        except subprocess.CalledProcessError:
-            logger.warning("{} is not available.".format(key))
-            result[key] = False
-            
-    return result
-
-def download_fw():  # from https://stackoverflow.com/a/16518224
-    temp_dir = mkdtemp()
-    logger.info("Temporary directory: {}".format(temp_dir))
-    os.chdir(temp_dir)  # Go to temporary directory
-    
-    u = urllib2.urlopen(fwurl)
-
-    scheme, netloc, path, query, fragment = urlparse.urlsplit(fwurl)
-    filename = os.path.basename(path)
-    if not filename:
-        filename = 'downloaded.file'
-
-    with open(filename, 'wb') as f:
-        meta = u.info()
-        meta_func = meta.getheaders if hasattr(meta, 'getheaders') else meta.get_all
-        meta_length = meta_func("Content-Length")
-        file_size = None
-        if meta_length:
-            file_size = int(meta_length[0])
-        logger.info("Downloading: {0} Bytes: {1}".format(fwurl, file_size))
-
-        file_size_dl = 0
-        block_sz = 8192
-        while True:
-            buffer = u.read(block_sz)
-            if not buffer:
-                break
-
-            file_size_dl += len(buffer)
-            f.write(buffer)
-
-            status = "{0:16}".format(file_size_dl)
-            if file_size:
-                status += "   [{0:6.2f}%]".format(file_size_dl * 100 / file_size)
-            status += chr(13)
-            logger.info(status)
-    
-    with ZipFile(filename, mode='r') as z:
-        fw_path = z.extract('dstat-firmware.hex')
-    
-    return fw_path
-
-def test_firmware_version(current=None):
-    if current is None:
-        current = state.firmware_version
-    
-    temp_dir = mkdtemp()
-    logger.info("Temporary directory: {}".format(temp_dir))
-    os.chdir(temp_dir)  # Go to temporary directory
-    
-    command = "git clone http://microfluidics.utoronto.ca/gitlab/dstat/dstat-firmware.git"
-    logger.info('Cloning master.')
-    try:
-        output = subprocess.check_output(command.split(),
-                                         stderr=subprocess.STDOUT)
-    except subprocess.CalledProcessError as e:
-        logger.error("git failed with error code {}".format(e.returncode))
-        logger.error("Output: {}".format(e.output))
-        return False, None
-    logger.info(output)
-    
-    os.chdir("./dstat-firmware")
-    
-    command = "git rev-parse --short master"
-    master = subprocess.check_output(command.split(), stderr=subprocess.STDOUT)
-    logger.info("Current master commit: {}".format(master))
-    
-    command = "git merge-base --is-ancestor master {}".format(current)
-    test = subprocess.call(command.split())
-    
-    if test == 0:  # already newest
-        logger.info('Firmware is latest available.')
-        return 'latest', master
-    elif test == 1:  # old version
-        logger.info('Firmware is out of date.')
-        return 'old', master
-    elif test == 128:  # newer or different branch
-        logger.info('Firmware is not on the master branch.')
-        return 'devel', master
-    else:
-        logger.error('Unexpected git error. Git exited {}'.format(test))
-        return False, None
-
-def dfu_program(path='./dstat-firmware.hex'):
-    """Tries to program DStat over USB with DFU with hex file at path."""
-    try:
-        command = "dfu-programmer atxmega256a3u erase"
-        output = subprocess.check_output(command.split(),
-                                          stderr=subprocess.STDOUT)
-        logger.info("%s\n%s", command, output)
-        command = "dfu-programmer atxmega256a3u flash {}".format(path)
-        output = subprocess.check_output(command.split(),
-                                          stderr=subprocess.STDOUT)
-        logger.info("%s\n%s", command, output)
-        command = "dfu-programmer atxmega256a3u launch"
-        output = subprocess.check_output(command.split(),
-                                          stderr=subprocess.STDOUT)
-        logger.info("%s\n%s", command, output)
-    except subprocess.CalledProcessError as e:
-        logger.error("{} failed with output:".format(" ".join(e.cmd)))
-        logger.error(e.output)
-        raise
-
-
-def dstat_enter_dfu():
-    """Tries to contact DStat and get version. Stores version in state.
-    If no response, returns False, otherwise True.
-        
-    Arguments:
-    ser_port -- address of serial port to use
-    """
-    exp = DFUMode()
-    state.ser.start_exp(exp)
-    while True:
-        result = state.ser.get_proc(block=True)
-        if result in ('SERIAL_ERROR', 'DONE'):
-            break
-    logger.info(result)
-    # state.ser.disconnect()
-
-    time.sleep(.1)
-    
-    return True
-
-
-class DFUMode(object):
-    def __init__(self):
-        pass
-        
-    def run(self, ser, ctrl_pipe, data_pipe):
-        """Tries to contact DStat and get version. Returns a tuple of
-        (major, minor). If no response, returns empty tuple.
-            
-        Arguments:
-        ser_port -- address of serial port to use
-        """
-        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'SF\n')
-                    exp_logger.info('SF')
-                    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
-
-
-if __name__ == "__main__":
-    log_handler = logging.StreamHandler()
-    log_formatter = logging.Formatter(
-                        fmt='%(asctime)s %(levelname)s: [%(name)s] %(message)s',
-                        datefmt='%H:%M:%S'
-                    )
-    log_handler.setFormatter(log_formatter)
-    logger.setLevel(level=logging.INFO)
-    logger.addHandler(log_handler)
-        
-    dstat_enter_dfu()
-    time.sleep(2)
-    dfu_program(sys.argv[1])
\ No newline at end of file
diff --git a/dstat_interface/core/dstat/experiment_handler.py b/dstat_interface/core/dstat/experiment_handler.py
new file mode 100644
index 0000000..5daa436
--- /dev/null
+++ b/dstat_interface/core/dstat/experiment_handler.py
@@ -0,0 +1,115 @@
+from datetime import datetime
+import logging
+import struct
+from typing import Callable, Union, Sequence, Tuple
+
+from .comm import SerialConnection
+
+
+logger = logging.getLogger(__name__)
+dstat_logger = logging.getLogger("{}.DSTAT".format(__name__))
+exp_logger = logging.getLogger("{}.Experiment".format(__name__))
+
+
+class ExperimentHandler(object):
+    def __init__(self, ser: SerialConnection, data_cols: Sequence[str] = ('voltage', 'current'),
+                 data_format: str = 'vA'):
+
+        self.ser = ser
+        self.data = {'timestamp': [], 'scan': []}
+        self.data |= {i: [] for i in data_cols}
+        self.data_cols = data_cols
+        self.data_convert = [self.get_data_converter(i) for i in data_format]
+        struct_formats = ['H' if i.islower() else 'l' for i in data_format]
+        self.struct = struct.Struct(f'<{"".join(struct_formats)}')
+        self.done = False
+
+        self.adc_trim = 0
+        self.adc_gain = 3000
+        self.adc_pga = 2
+
+        self.first_skipped = False
+
+    def get_data_converter(self, fmt: str) -> Union[Callable, None]:
+        converters = {'v': self.dac_to_volts, 'A': self.adc_to_amps, 'm': self.ms_to_s}
+        try:
+            return converters[fmt]
+        except KeyError:
+            return None
+
+    @staticmethod
+    def dac_to_volts(dac: int) -> float:
+        return (dac - 32768) * 3000. / 65536
+
+    def adc_to_amps(self, adc: int) -> float:
+        return (adc + self.adc_trim) * (1.5 / self.adc_gain / 8388607) / (self.adc_pga / 2)
+
+    @staticmethod
+    def ms_to_s(ms: int) -> float:
+        return ms/1000.
+
+    def experiment_running_data(self):
+        """Receive data from experiment process and add to
+        current_exp.data['data].
+        """
+        try:
+            incoming = self.ser.get_data()
+            if incoming is not None:
+                if not self.first_skipped:
+                    self.first_skipped = True
+                    return True
+                self.data_handler(incoming)
+            return True
+
+        except EOFError as err:
+            logger.error(err)
+            return False
+        except IOError as err:
+            logger.error(err)
+            return False
+
+    def get_all_data(self):
+        """
+        Processes remaining data in queue
+        """
+        while True:
+            incoming = self.ser.get_data()
+            if incoming is not None:
+                self.data_handler(incoming)
+            else:
+                self.done = True
+                return
+
+    def data_handler(self, data_input: Tuple[datetime, int, bytes]):
+        date, scan, data = data_input
+        unpacked = self.struct.unpack(data)
+        self.data['scan'].append(scan)
+        self.data['timestamp'].append(date)
+
+        for n, i in enumerate(unpacked):
+            try:
+                self.data[self.data_cols[n]].append(self.data_convert[n](i))
+            except TypeError:  # If no converter
+                self.data[self.data_cols[n]].append(i)
+
+    def experiment_running_proc(self):
+        """Receive proc signals from experiment process."""
+        try:
+            proc_buffer = self.ser.get_proc()
+            if proc_buffer is not None:
+                if proc_buffer in ["DONE", "SERIAL_ERROR", "ABORT"]:
+                    if proc_buffer == "SERIAL_ERROR":
+                        self.ser.close()
+
+                else:
+                    logger.warning("Unrecognized experiment return code: %s",
+                                   proc_buffer)
+                return False
+            return True
+
+        except EOFError as err:
+            logger.warning("EOFError: %s", err)
+            return False
+        except IOError as err:
+            logger.warning("IOError: %s", err)
+            return False
diff --git a/dstat_interface/core/dstat/experiment_process.py b/dstat_interface/core/dstat/experiment_process.py
new file mode 100644
index 0000000..b82ea0b
--- /dev/null
+++ b/dstat_interface/core/dstat/experiment_process.py
@@ -0,0 +1,246 @@
+import logging
+import time
+from abc import ABC, abstractmethod
+from multiprocessing.connection import Connection
+from typing import Union
+from functools import singledispatchmethod
+
+import serial
+
+logger = logging.getLogger(__name__)
+dstat_logger = logging.getLogger("{}.DSTAT".format(__name__))
+exp_logger = logging.getLogger("{}.Experiment".format(__name__))
+
+
+class BaseExperimentProcess(ABC):
+    def __init__(self, *args, **kwargs):
+        self.data_bytes = 0
+        self.serial: Union[None, serial.Serial] = None
+        self.ctrl_pipe: Union[None, Connection] = None
+        self.data_pipe: Union[None, Connection] = None
+
+        self.commands = []
+
+    @singledispatchmethod
+    def parse_command_string(self, cmd_str, params: dict):
+        raise NotImplementedError("cmd_str has unrecognized type %s", type(cmd_str))
+
+    @parse_command_string.register
+    def _(self, cmd_str: str, params: dict):
+        self.commands += [cmd_str.format(**params)]
+
+    @parse_command_string.register
+    def _(self, cmd_str: tuple, params: dict):
+        cmd: str
+        data: tuple
+        cmd, data = cmd_str
+
+        cmd = cmd.format(**params)
+        data_parsed = []
+        for i in data:
+            data_parsed += [str(i) for i in params[i]]
+
+        self.commands += [(cmd, data_parsed)]
+
+    def write_command(self, cmd, params=None, retry=5):
+        """Write command to serial with optional number of retries."""
+
+        def get_reply(retries=3):
+            while True:
+                reply = self.serial.readline().rstrip().decode('ascii')
+                print(reply)
+                if reply.startswith('#'):
+                    dstat_logger.info(reply)
+                elif reply == "":
+                    retries -= 1
+                    if retries <= 0:
+                        raise serial.SerialException()
+                else:
+                    return reply
+
+        n = len(cmd)
+        if params is not None:
+            n_params = len(params)
+
+        for _ in range(retry):
+            tries = 5
+            while True:
+                time.sleep(0.2)
+                self.serial.reset_input_buffer()
+                self.serial.write(f'!{n}\n'.encode('ascii'))
+                time.sleep(.1)
+
+                try:
+                    reply = get_reply()
+                except serial.SerialException:
+                    if tries <= 0:
+                        continue
+                    tries -= 1
+                    pass
+                else:
+                    break
+
+            if reply != f"@ACK {n}":
+                logger.warning("Expected ACK got: {}".format(reply))
+                continue
+
+            tries = 5
+
+            while True:
+                self.serial.write(f'{cmd}\n'.encode('ascii'))
+                print(cmd)
+                try:
+                    reply = get_reply()
+                except serial.SerialException:
+                    if tries <= 0:
+                        continue
+                    tries -= 1
+                    pass
+                else:
+                    break
+
+            if reply != f"@RCV {n}":
+                logger.warning("Expected RCV got: {}".format(reply))
+                continue
+
+            if params is None:
+                return True
+
+            tries = 5
+
+            while True:
+                try:
+                    reply = get_reply()
+                except serial.SerialException:
+                    if tries <= 0:
+                        continue
+                    tries -= 1
+                    pass
+                else:
+                    break
+
+            if reply != f"@RQP {n_params}":
+                logger.warning("Expected RQP %s got: %s", n_params, reply)
+                continue
+
+            tries = 5
+
+            for i in params:
+                while True:
+                    self.serial.write(f"{i} ".encode('ascii'))
+                    try:
+                        reply = get_reply()
+                        if reply == f"@RCVC {i}":
+                            break
+                    except serial.SerialException:
+                        if tries <= 0:
+                            continue
+                        tries -= 1
+                        pass
+                    else:
+                        break
+            return True
+        return False
+
+    def run(self, ser: serial.Serial, ctrl_pipe: Connection, data_pipe: Connection):
+        """Execute experiment. Connects and sends handshake signal to DStat
+        then sends self.commands. Don't call directly as a process in Windows,
+        use run_wrapper instead.
+        """
+        self.serial = ser
+        self.ctrl_pipe = ctrl_pipe
+        self.data_pipe = data_pipe
+
+        exp_logger.info('Experiment running')
+        status = 'DONE'
+
+        try:
+            for i in self.commands:
+                status = 'DONE'
+                if isinstance(i, str):
+                    logger.info('Command: %s', i)
+                    print(f'command: {i}')
+
+                    if not self.write_command(i):
+                        status = 'ABORT'
+                        break
+
+                else:
+                    cmd, data = i
+                    print(f'command: {i}')
+                    logger.info("Command: %s", cmd)
+
+                    if not self.write_command(cmd, params=data):
+                        status = 'ABORT'
+                        break
+
+                if not self.serial_handler():
+                    status = 'ABORT'
+                    break
+
+                time.sleep(0.25)
+
+        except serial.SerialException:
+            status = 'SERIAL_ERROR'
+        finally:
+            while self.ctrl_pipe.poll():
+                self.ctrl_pipe.recv()
+        return status
+
+    @abstractmethod
+    def serial_handler(self):
+        pass
+
+
+class ExperimentProcess(BaseExperimentProcess):
+    def __init__(self):
+        """Adds commands for gain and ADC."""
+        super(ExperimentProcess, self).__init__()
+        self.datapoint = 0
+        self.scan = 0
+        self.time = 0
+
+    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.data_pipe as result of self.data_handler).
+        """
+        scan = 0
+        start = None
+        try:
+            while True:
+                if self.ctrl_pipe.poll():
+                    ctrl = self.ctrl_pipe.recv()
+                    logger.debug("serial_handler: %s", ctrl)
+                    if ctrl == "DISCONNECT":
+                        self.serial.write(b'a')
+                        self.serial.reset_input_buffer()
+                        logger.info("serial_handler: ABORT pressed!")
+                        time.sleep(.3)
+                        return False
+                    elif ctrl == 'a':
+                        self.serial.write(b'a')
+
+                for line in self.serial:
+                    if self.ctrl_pipe.poll():
+                        if self.ctrl_pipe.recv() == 'a':
+                            self.serial.write(b'a')
+
+                    if line.startswith(b'B'):
+                        if not start:
+                            start = time.perf_counter()
+                        data = (time.perf_counter()-start, scan, self.serial.read(size=self.data_bytes))
+                        self.data_pipe.send(data)
+
+                    elif line.lstrip().startswith(b'S'):
+                        scan += 1
+
+                    elif line.lstrip().startswith(b"#"):
+                        dstat_logger.info(line.strip().decode('ascii'))
+
+                    elif line.lstrip().startswith(b"@DONE"):
+                        dstat_logger.debug(line.strip().decode('ascii'))
+                        return True
+
+        except serial.SerialException:
+            return False
diff --git a/dstat_interface/core/dstat/simulator.py b/dstat_interface/core/dstat/simulator.py
new file mode 100644
index 0000000..575f781
--- /dev/null
+++ b/dstat_interface/core/dstat/simulator.py
@@ -0,0 +1,403 @@
+from copy import copy
+from io import IOBase
+from math import ceil, floor
+import logging
+import struct
+from time import sleep
+import re
+from typing import Callable, Union
+
+import numpy as np
+
+logger = logging.getLogger("dstat.simulator")
+digit_pattern = re.compile(rb'(\d+)')
+
+
+class Simulator(object):
+    default_settings = {'max5443_offset': 0,
+                        'tcs_enabled': 1,
+                        'tcs_clear_threshold': 10000,
+                        'r100_trim': 0,
+                        'r3k_trim': 0,
+                        'r30k_trim': 0,
+                        'r300k_trim': 0,
+                        'r3M_trim': 0,
+                        'r30M_trim': 0,
+                        'r100M_trim': 0,
+                        'eis_cal1': 0,
+                        'eis_cal2': 0,
+                        'dac_units_true': 1}
+
+    def __init__(self):
+        self.current_state = self.main
+        self.next_state: Union[None, Callable] = None
+        self.output: bytes = b''
+        self.input_str: bytes = b''
+        self.cmd_length: int = 0
+        self.command_str: bytes = b''
+
+        self.waiting_for_params: int = 0
+        self.extra_params = []
+        self.current_cmd: bytes = b''
+        self.current_params: list = []
+
+        self.settings_dict = copy(Simulator.default_settings)
+
+    def _get_input(self, size=1) -> bytes:
+        if size > len(self.input_str):
+            logger.error("Not enough characters in input buffer")
+            raise IndexError("Not enough characters in input buffer")
+
+        string = self.input_str[:size]
+        self.input_str = self.input_str[size:]
+        return string
+
+    def _get_command(self):
+        string = self.command_str[:1]
+        self.command_str = self.command_str[1:]
+        return string
+
+    def _get_params(self, n):
+        params = self.command_str.split(None, n)
+        if len(params) > n:
+            self.input_str = params.pop()
+        elif len(params) < n:
+            raise IndexError("Not enough characters in command buffer")
+        return params
+
+    def input(self, string):
+        logger.debug("input: {}".format(string))
+        self.input_str = string
+        while len(self.input_str) > 0:
+            self.current_state()
+
+    def main(self):
+        char = self._get_input()
+
+        if char == b'!':
+            try:
+                match = re.match(digit_pattern, self.input_str)
+                cmd_length = match.group(1)
+            except AttributeError:
+                logger.error("No character count received")
+                self.input_str = b''
+                return
+            self.cmd_length = int(cmd_length)
+            self.input_str = self.input_str[len(cmd_length) + 1:]
+            self.output += f'@ACK {self.cmd_length}\n'.encode('ascii')
+            if self.cmd_length == 0:
+                self.output += b'@RCV 0\n'
+                return
+            self.current_state = self.command
+
+            if self.input_str:
+                self.current_state()
+
+    def command(self):
+        def restart():
+            logger.info("USB restart received")
+            return
+
+        def version():
+            logger.info("Version Check")
+            self.output += f'V1.2.3-{0xfffffff:d}\n'.encode('ascii')
+            return
+
+        command_map = {b'E': self.experiment,
+                       b'S': self.settings,
+                       b'R': restart,
+                       b'V': version}
+
+        self.command_str = self._get_input(self.cmd_length + 1)
+        self.output += f'@RCV {self.cmd_length}\n'.encode('ascii')
+
+        char = self.command_str[:1]
+        self.command_str = self.command_str[1:-1]  # Remove delimiter
+
+        try:
+            if command_map[char]():
+                self.current_state = self.input_params
+                return
+        except KeyError:
+            logger.warning("Unrecognized command {}".format(char))
+            self.output += f"#ERR: Command {char} not recognized\n".encode('ascii')
+
+        if self.current_state.__name__ != self.abort_wait.__name__:
+            self.output += b"@DONE\n"
+            self.current_state = self.main
+        else:
+            logger.info("abort_wait state")
+        logger.info("Command {} Finished".format(char))
+
+    def abort_wait(self):
+        logger.info("abort_wait() called")
+        self.current_state = self.abort_wait
+        if self._get_input() == b'a':
+            logger.info("Abort signal received")
+            self.output += b"@DONE\n"
+            self.current_state = self.main
+
+    def input_params(self):
+        param = self._get_params(1)
+        self.extra_params.append(param)
+        self.output += f'@RCVC {param}\n'.encode('ascii')
+        self.waiting_for_params -= 1
+
+        if self.waiting_for_params == 0:
+            self.next_state()
+
+    def settings(self):
+        def reset():
+            logger.info("Settings reset")
+            self.settings_dict = self.default_settings.copy()
+
+        def firmware():
+            logger.info("Firmware update mode")
+
+        def read():
+            self.output += b"S"
+            for key, value in self.settings_dict.items():
+                self.output += f"{key}.{value}:".encode('ascii')
+            self.output = self.output.rstrip(b':')
+            self.output += b"\n"
+
+        def write():
+            params = self._get_params(10)
+            for n, i in enumerate(self.settings_dict):
+                self.settings_dict[i] = int(params[n])
+
+        settings_map = {b'D': reset,
+                        b'F': firmware,
+                        b'R': read,
+                        b'W': write
+                        }
+
+        char = self.command_str[:1]
+        self.command_str = self.command_str[1:]
+
+        try:
+            settings_map[char]()
+        except KeyError:
+            logger.warning("Settings control %s not found", char)
+
+    def experiment(self):
+        def ads1255():
+            params = self._get_params(3)
+            logger.info("ADS1255 params: %s", params)
+            self.output += f'#A: {params[0]} {params[1]} {params[2]}\n'.encode('ascii')
+
+        def gain():
+            params = self._get_params(2)
+            logger.info("IV gain: %s", params)
+            self.output += f'#G: {params[0]} {params[1]}\n'.encode('ascii')
+
+        def lsv():
+            params = self._get_params(7)
+            start = int(params[-3])
+            stop = int(params[-2])
+            slope = int(params[-1])
+
+            logger.info("LSV params: %s", params)
+
+            for i in np.arange(start, stop, slope//10):
+                self.output += b"B\n"
+                self.output += struct.pack('<Hl', i, 500 * (i-32698))
+                self.output += b"\n"
+
+        def cv():
+            params = self._get_params(9)
+            start = int(params[-3])
+            v1 = int(params[-5])
+            v2 = int(params[-4])
+            scans = int(params[-2])
+            slope = int(params[-1])
+
+            logger.info("CV params: %s", params)
+
+            for scan in range(scans):
+                for i in np.arange(start, v1, slope//100):
+                    self.output += b"B\n"
+                    self.output += struct.pack('<Hl', i, 500 * (i-32698) + 100 * scan)
+                    self.output += b"\n"
+                for i in np.arange(v1, v2, slope//100):
+                    self.output += b"B\n"
+                    self.output += struct.pack('<Hl', i, 500 * (i - 32698) + 100 * scan)
+                    self.output += b"\n"
+                for i in np.arange(v2, start, slope//100):
+                    self.output += b"B\n"
+                    self.output += struct.pack('<Hl', i, 500 * (i - 32698) + 100 * scan)
+                    self.output += b"\n"
+                self.output += b"S\n"
+            self.output += b"D\n"
+
+        def swv():
+            params = self._get_params(10)
+            start = int(params[-6])
+            stop = int(params[-5])
+            step = int(int(params[-4])*3000/65536.)
+            scans = int(params[-1])
+
+            logger.info("SWV params: %s", params)
+
+            if scans < 1:
+                scans = 1
+
+            for scan in range(scans):
+                for i in np.arange(start, stop, step):
+                    self.output += b"B\n"
+                    self.output += struct.pack('<Hll', i, 500 * (i - 32698) + 100 * scan, 0)
+                    self.output += b"\n"
+                for i in np.arange(stop, start, step):
+                    self.output += b"B\n"
+                    self.output += struct.pack('<Hll', i, 500 * (i - 32698) + 100 * scan, 0)
+                    self.output += b"\n"
+                self.output += b"S\n"
+            self.output += b"D\n"
+
+        def dpv():
+            params = self._get_params(10)
+            start = int(params[-6])
+            stop = int(params[-5])
+
+            logger.info("DPV params: %s", params)
+
+            for i in np.arange(start, stop, 1):
+                self.output += b"B\n"
+                self.output += struct.pack('<Hll', i, 500 * (i - 32698), 0)
+                self.output += b"\n"
+
+            self.output += b"D\n"
+
+        def pmt():
+            logger.info("PMT Idle mode entered")
+            self.abort_wait()
+
+        def pot():
+            params = self._get_params(2)
+            seconds = int(params[0])
+            logger.info("POT params: %s", params)
+
+            if seconds == 0:
+                self.abort_wait()
+            else:
+                for i in range(seconds):
+                    self.output += b"B\n"
+                    self.output += struct.pack('<Hll', i, 0, 100)
+
+        def ca():
+            params = self._get_params(2)
+            steps = int(params[0])
+            tcs = int(params[1])
+
+            self.current_params = [steps, tcs]
+            self.waiting_for_params = steps * 2
+            self.output += f"@RQP {steps * 2}\n".encode("ascii")
+            self.next_state = self.experiment
+            return True
+
+        def ca_params(steps, tcs):
+            times = [int(i) for i in self.extra_params[steps:]]
+            seconds = sum(times)
+
+            for i in range(seconds):
+                logger.info(i)
+                self.output += b"B\n"
+                self.output += struct.pack('<HHl', i, 0, 100 * i)
+                self.output += b"\n"
+                sleep(1)
+            self.extra_params = []
+
+        def sync():
+            params = self._get_params(1)
+            logger.info('Shutter Sync %s Hz', params[0])
+
+        def shut_off():
+            logger.info('Shutter Sync Off')
+
+        def shut_close():
+            logger.info('Shutter closed')
+
+        def shut_open():
+            logger.info('Shutter open')
+
+        experiment_map = {b'A': ads1255,
+                          b'G': gain,
+                          b'L': lsv,
+                          b'C': cv,
+                          b'S': swv,
+                          b'D': dpv,
+                          b'M': pmt,
+                          b'P': pot,
+                          b'R': ca,
+                          b'Z': sync,
+                          b'z': shut_off,
+                          b'1': shut_close,
+                          b'2': shut_open
+                          }
+
+        experiment_post_params_map = {b'R': ca_params}
+
+        if self.next_state:
+            experiment_post_params_map[self.current_cmd](*self.current_params)
+            self.current_params = []
+            self.next_state = None
+        else:
+            char = self._get_command()
+
+            try:
+                return experiment_map[char]()
+            except KeyError:
+                logger.warning('Unrecognized exp command %s', char)
+                self.output += f'#ERR: Command {char} not recognized\n'.encode('ascii')
+
+
+class SerialSim(IOBase):
+    def __init__(self, *args, **kwargs):
+        self.sim = Simulator()
+        self.is_open = True
+
+    def open(self):
+        self.is_open = True
+
+    def close(self):
+        self.is_open = False
+        self.reset_input_buffer()
+
+    def write(self, string: bytes):
+        self.sim.input(string)
+
+    def read(self, size=1) -> bytes:
+        output = self.sim.output[0:size]
+        self.sim.output = self.sim.output[size:]
+        return output
+
+    def reset_input_buffer(self):
+        self.sim.output = b""
+
+    def next(self) -> bytes:
+        if self.sim.output == b"":
+            raise StopIteration
+
+        return self.readline()
+
+    def readline(self, size=-1) -> bytes:
+        if len(self.sim.output) == 0:
+            return b""
+
+        if size > 0:
+            output, sep, remain = self.sim.output[:size].partition(b'\n')
+        else:
+            output, sep, remain = self.sim.output.partition(b'\n')
+
+        if sep != b'\n':
+            return b""
+
+        self.sim.output = self.sim.output[len(output) + 1:]
+
+        return output + b'\n'
+
+    def __iter__(self):
+        return self
+
+    def _update_dtr_state(self):
+        pass
diff --git a/dstat_interface/core/dstat/state.py b/dstat_interface/core/dstat/state.py
index 12262c4..538c4a4 100644
--- a/dstat_interface/core/dstat/state.py
+++ b/dstat_interface/core/dstat/state.py
@@ -1,14 +1,25 @@
-from collections import OrderedDict
-
-def reset():
-    settings = OrderedDict()
-    ser = None
-    dstat_version = None
-    firmware_version = None
-    board_instance = None
-
-settings = OrderedDict()
-ser = None
-dstat_version = None
-firmware_version = None
-board_instance = None
\ No newline at end of file
+from __future__ import annotations
+from typing import Union, TYPE_CHECKING
+
+from dstat_interface.utils import Singleton
+
+if TYPE_CHECKING:
+    from pkg_resources._vendor.packaging import version
+    from dstat_interface.dstat.comm import SerialConnection
+    from dstat_interface.dstat.boards import BaseBoard
+
+
+class DStatState(object, metaclass=Singleton):
+    def __init__(self):
+        self.settings = {}
+        self.ser: Union[None, SerialConnection] = None
+        self.dstat_version: Union[None, version.Version] = None
+        self.firmware_version: Union[None, str] = None
+        self.board_instance: Union[None, BaseBoard] = None
+
+    def reset(self):
+        self.settings = {}
+        self.ser = None
+        self.dstat_version = None
+        self.firmware_version = None
+        self.board_instance = None
diff --git a/dstat_interface/core/dstat/test/test_comm.py b/dstat_interface/core/dstat/test/test_comm.py
new file mode 100644
index 0000000..9e776a2
--- /dev/null
+++ b/dstat_interface/core/dstat/test/test_comm.py
@@ -0,0 +1,41 @@
+import pytest
+from pkg_resources import parse_version
+
+from dstat_interface.dstat.comm import dstat_connect, read_settings, write_settings
+from dstat_interface.dstat import simulator, state
+
+
+class TestDStat:
+    @pytest.yield_fixture(autouse=True)
+    def dstat_connect_simulator(self):
+        dstat_connect('simulator')
+        yield
+        state.ser.close()
+
+    def test_dstat_connect(self):
+        assert state.dstat_version == parse_version('1.2.3') and state.firmware_version == 'fffffff'
+
+    def test_read_settings(self):
+        read_settings()
+        assert state.settings == simulator.Simulator.default_settings
+
+    def test_write_settings(self):
+        new_settings = dict([('max5443_offset', 200),
+                             ('tcs_enabled', 0),
+                             ('tcs_clear_threshold', 19000),
+                             ('r100_trim', 23),
+                             ('r3k_trim', 14),
+                             ('r30k_trim', 56),
+                             ('r300k_trim', 3),
+                             ('r3M_trim', 2),
+                             ('r30M_trim', -34),
+                             ('r100M_trim', -60)
+                             ('eis_cal1', 0),
+                             ('eis_cal2', 0),
+                             ('dac_units_true', 1)
+                             ])
+        state.settings = new_settings
+        write_settings()
+        state.settings = {}
+        read_settings()
+        assert state.settings == new_settings
diff --git a/dstat_interface/core/dstat/utility.py b/dstat_interface/core/dstat/utility.py
new file mode 100644
index 0000000..00860fb
--- /dev/null
+++ b/dstat_interface/core/dstat/utility.py
@@ -0,0 +1,123 @@
+import logging
+from multiprocessing.connection import Connection
+from typing import Union
+
+from serial import Serial, SerialException
+
+from .experiment_process import BaseExperimentProcess
+from dstat_interface.core.dstat.state import DStatState
+
+logger = logging.getLogger(__name__)
+dstat_logger = logging.getLogger("{}.DSTAT".format(__name__))
+exp_logger = logging.getLogger("{}.Experiment".format(__name__))
+
+state = DStatState()
+
+
+def abs_mv_to_dac(mv: float) -> int:
+    dac = int(mv / state.board_instance.re_voltage_scale * (65536. / 3000) + 32768)
+    assert 0 <= dac <= 65535
+    return dac
+
+
+def rel_mv_to_dac(mv: float) -> int:
+    dac = int(mv / state.board_instance.re_voltage_scale * (65536. / 3000))
+    assert 0 < dac <= 65535  # DStat params that take relative mv don't accept 0
+    return dac
+
+
+def param_test_uint16(u: int) -> int:
+    assert 0 <= u <= 65535
+    return u
+
+
+def param_test_non_zero_uint16(u: int) -> int:
+    assert 0 < u <= 65535
+    return u
+
+
+def param_test_uint8(u: int) -> int:
+    assert 0 < u <= 255
+    return u
+
+
+def param_test_non_zero_uint8(u: int) -> int:
+    assert 0 < u <= 255
+    return u
+
+
+class VersionCheckProcess(BaseExperimentProcess):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.commands += ['V', 'X']
+
+    def serial_handler(self):
+        try:
+            while True:
+                for line in self.serial:
+                    if line.lstrip().startswith(b"#"):
+                        dstat_logger.info(line.strip().decode('ascii'))
+                    elif line.lstrip().startswith(b"@DONE"):
+                        dstat_logger.debug(line.strip().decode('ascii'))
+                        return True
+                    elif line.startswith(b'V'):
+                        ver_str = line.lstrip(b'V').decode('ascii')
+                        pcb, sep, firmware = ver_str.strip().rpartition('-')
+
+                        if pcb == "":
+                            pcb = firmware
+                            logger.info('Your firmware does not support PCB version detection.')
+                            self.data_pipe.send(('V', (pcb, False)))
+                        else:
+                            logger.info(f"Firmware Version: {hex(int(firmware)).lstrip('0x')}")
+                            self.data_pipe.send(('V', (pcb, hex(int(firmware)).lstrip('0x'))))
+
+                        logger.info(f'PCB Version: {pcb}')
+                    elif line.startswith(b'X'):
+                        mux_str = line.lstrip(b'X').decode('ascii')
+                        self.data_pipe.send(('X', int(mux_str)))
+
+        except SerialException:
+            return False
+
+
+class SettingsProcess(BaseExperimentProcess):
+    def __init__(self, task, settings: Union[None, dict[str, str]] = None, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.task = task
+        self.settings = settings
+
+        self.serial: Union[None, Serial] = None
+        self.ctrl_pipe: Union[None, Connection] = None
+        self.data_pipe: Union[None, Connection] = None
+
+        if self.task == 'w':
+            write_buffer = [str(i) for i in self.settings.values()]
+            to_write = ' '.join(write_buffer) + ' '
+            logger.debug('to_write = %s', to_write)
+
+            self.commands += ['SW' + to_write]
+
+        elif self.task == 'r':
+            self.commands += ['SR']
+
+    def serial_handler(self):
+        ser_logger = logging.getLogger(f'{__name__}._serial_process')
+        try:
+            while True:
+                for line in self.serial:
+                    if line.lstrip().startswith(b"#"):
+                        ser_logger.info(line.strip().decode('ascii'))
+
+                    elif line.lstrip().startswith(b"@DONE"):
+                        ser_logger.debug(line.strip().decode('ascii'))
+                        return True
+
+                    if line.lstrip().startswith(b'S'):
+                        input_line = line.lstrip()[1:].decode('ascii')
+                        parted = input_line.rstrip().split(':')
+                        settings = {key: int(value) for key, value in [i.split('.') for i in parted]}
+
+                        self.data_pipe.send(settings)
+        except SerialException:
+            return False
diff --git a/dstat_interface/core/errors.py b/dstat_interface/core/errors.py
deleted file mode 100755
index 9661262..0000000
--- a/dstat_interface/core/errors.py
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/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/>.
-
-class Error(Exception):
-    """Copies Exception class"""
-    pass
-
-class InputError(Error):
-    """Exception raised for errors in the input. Extends Error class.
-        
-    Attributes:
-        expr -- input expression in which the error occurred
-        msg  -- error message
-    """
-    
-    def __init__(self, expr, msg):
-        self.expr = expr
-        self.msg = msg
-
-class VarError(Error):
-    """Exception raised for internal variable errors. Extends Error class.
-        
-    Attributes:
-        var -- var in which the error occurred
-        msg  -- error message
-    """
-    
-    def __init__(self, var, msg):
-        self.var = var
-        self.msg = msg
\ No newline at end of file
diff --git a/dstat_interface/core/experiments/__init__.py b/dstat_interface/core/experiments/__init__.py
deleted file mode 100644
index 6991a97..0000000
--- a/dstat_interface/core/experiments/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# __all__ = []
-#
-# import pkgutil
-# import inspect
-# from . import cal, chronoamp, cv
-#
-#
-# for loader, name, is_pkg in pkgutil.walk_packages(__path__):
-#     print loader, name, is_pkg
-#     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
diff --git a/dstat_interface/core/experiments/cal.py b/dstat_interface/core/experiments/cal.py
deleted file mode 100755
index 3da9280..0000000
--- a/dstat_interface/core/experiments/cal.py
+++ /dev/null
@@ -1,139 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#     DStat Interface - An interface for the open hardware DStat potentiostat
-#     Copyright (C) 2017  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 time
-import struct
-import logging
-logger = logging.getLogger(__name__)
-
-import serial
-
-from ..errors import InputError, VarError
-from ..dstat import state
-from ..experiments.experiment_template import Experiment, dstat_logger
-
-
-def measure_offset(time):
-    gain_trim_table = [None, 'r100_trim', 'r3k_trim', 'r30k_trim', 'r300k_trim',
-                        'r3M_trim', 'r30M_trim', 'r100M_trim']
-    
-    parameters = {}
-    parameters['time'] = time
-    
-    gain_offset = {}
-    
-    for i in range(1,8):
-        parameters['gain'] = i
-        state.ser.start_exp(CALExp(parameters))
-        logger.info("measure_offset: %s", state.ser.get_proc(block=True))
-        gain_offset[gain_trim_table[i]] = state.ser.get_data(block=True)
-        
-    return gain_offset
-
-
-class CALExp(Experiment):
-    id = 'cal'
-    """Offset calibration experiment"""
-    def __init__(self, parameters):
-        self.parameters = parameters
-        self.databytes = 8
-        self.scan = 0
-        self.data = []
-
-        self.commands = ["EA2 3 1 ", "EG"]
-
-        self.commands[1] += str(self.parameters['gain'])
-        self.commands[1] += " "
-        self.commands[1] += "0 "
-
-        self.commands.append(
-            ("ER1 0", ["32768", str(self.parameters['time'])])
-        )
-
-        
-    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.data_pipe as result of self.data_handler).
-        """
-
-        try:
-            while True:
-                if self.ctrl_pipe.poll():
-                    input = self.ctrl_pipe.recv()
-                    logger.debug("serial_handler: %s", input)
-                    if input == ('a' or "DISCONNECT"):
-                        self.serial.write('a')
-                        logger.info("serial_handler: ABORT pressed!")
-                        return False
-                        
-                for line in self.serial:                    
-                    if self.ctrl_pipe.poll():
-                        if self.ctrl_pipe.recv() == 'a':
-                            self.serial.write('a')
-                            logger.info("serial_handler: ABORT pressed!")
-                            return False
-                            
-                    if line.startswith('B'):
-                        self.data.append(self.data_handler(
-                                        self.serial.read(size=self.databytes)))
-                        
-                    elif line.lstrip().startswith("#"):
-                        dstat_logger.info(line.lstrip().rstrip())
-                        
-                    elif line.lstrip().startswith("@DONE"):
-                        dstat_logger.debug(line.lstrip().rstrip())
-                        self.serial.flushInput()
-                        self.experiment_done()
-                        return True
-                        
-        except serial.SerialException:
-            return False
-            
-    def data_handler(self, data):
-        """Takes data_input as tuple -- (scan, data).
-        Returns:
-        current
-        """
-        
-        seconds, milliseconds, current = struct.unpack('<HHl', data)
-        return current
-    
-    def experiment_done(self):
-        """Averages data points
-        """
-        try:
-            sum = 0
-            self.data[0] = 0 # Skip first point
-
-        except IndexError:
-            return
-
-        for i in self.data:
-            sum += i
-        
-        sum /= len(self.data)
-        
-        if (sum > 32767):
-            sum = 32767
-        elif (sum < -32768):
-            sum = -32768
-        
-        self.data_pipe.send(sum)        
\ No newline at end of file
diff --git a/dstat_interface/core/experiments/chronoamp.py b/dstat_interface/core/experiments/chronoamp.py
index 94c7484..665ba64 100644
--- a/dstat_interface/core/experiments/chronoamp.py
+++ b/dstat_interface/core/experiments/chronoamp.py
@@ -1,170 +1,70 @@
-import time
-import struct
-import numpy as np
-import serial
-from ..interface.plot import mean, plotSpectrum, findBounds
-
-from .experiment_template import PlotBox, Experiment, exp_logger
-
-
-class ChronoampBox(PlotBox):
-    def setup(self):
-        self.plot_format = {
-            'current_time': {'xlabel': "Time (s)",
-                             'ylabel': "Current (A)"
-                            }
-        }
-
-    def format_plots(self):
-        """
-        Creates and formats subplots needed. Overrides superclass.
-        """
-        self.subplots = {'current_time': self.figure.add_subplot(111)}
-        
-        for key, subplot in self.subplots.items():
-            subplot.ticklabel_format(style='sci', scilimits=(0, 3),
-                                     useOffset=False, axis='y')
-            subplot.plot([],[])
-            subplot.set_xlabel(self.plot_format[key]['xlabel'])
-            subplot.set_ylabel(self.plot_format[key]['ylabel'])
-
-
-class Chronoamp(Experiment):
-    id = 'cae'
-    """Chronoamperometry experiment"""
-    def setup(self):
-        self.datatype = "linearData"
-        self.datalength = 2
-        self.databytes = 8
-        self.data = {'current_time' : [([],[])]}
-        self.columns = ['Time (s)', 'Current (A)']
-        self.total_time = sum(self.parameters['time'])
-        self.plotlims = {'current_time': {'xlims': (0, self.total_time)}
-                         }
-        
-        self.commands.append(
-            ("ER" + str(len(self.parameters['potential'])) + " 0 ", [])
-            )
-
-        for i in self.parameters['potential']:
-            self.commands[-1][1].append(str(int(i*(65536./3000)+32768)))
-        for i in self.parameters['time']:
-            self.commands[-1][1].append(str(i))
-
-        plot = ChronoampBox(['current_time'])
-        plot.setlims('current_time', **self.plotlims['current_time'])
-
-        self.plots.append(plot)
-      
-    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+self.gain_trim)*(1.5/self.gain/8388607)
-                       )
-                )
-
-    def store_data(self, incoming, newline):
-        """Stores data in data attribute. Should not be called from subprocess.
-        Can be overriden for custom experiments."""
-        line, data = incoming
-        
-        if newline is True:
-            self.data['current_time'].append(deepcopy(self.line_data))
-
-        for i, item in enumerate(self.data['current_time'][line]):
-            item.append(data[i])
+from datetime import datetime
+from typing import Any
+
+from ..dstat.experiment_handler import ExperimentHandler
+from ..dstat.experiment_process import ExperimentProcess
+from ..dstat.utility import abs_mv_to_dac, rel_mv_to_dac, param_test_uint16, param_test_non_zero_uint8
+from ..experiments.experiment_container import ExperimentContainer
+
+
+class CAExperimentHandler(ExperimentHandler):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.data['time'] = []
+
+    def data_handler(self, data_input: tuple[datetime, int, bytes]):
+        unpacked: tuple[int, int, int]
+        date, scan, data = data_input
+        unpacked = self.struct.unpack(data)
+        self.data['scan'].append(scan)
+        self.data['timestamp'].append(date)
+
+        for n, i in enumerate(unpacked):
+            try:
+                self.data[self.data_cols[n]].append(self.data_convert[n](i))
+            except TypeError:  # If no converter
+                self.data[self.data_cols[n]].append(i)
+        self.data['time'].append(self.data['time_s'][-1] + self.data['time_ms'][-1])
+
+
+class CAExperimentContainer(ExperimentContainer):
+    experiment_id = 'ca'
+    display_name = 'Chronoamperometry'
+    process = ExperimentProcess
+    handler = CAExperimentHandler
+    data_bytes = 0
+
+    def __init__(self, params: dict[str, Any], mux: int = 1):
+        super().__init__(params, mux=mux)
+
+        self.data_cols = ['time_s', 'time_ms', 'current']
+        self.data_format = 'smA'
+
+        self.cmd_str += [('ER{n_steps} 0 ', ('voltages', 'times'))]
+        self.param_input |= {'times': list, 'voltages': list}
+        self.param_input_display_names |= {'times': 'Time (s)', 'voltages': 'Voltage (mV)'}
+        self.param_tables = {'Steps:': ['times', 'voltages']}
+        self.param_input_limits |= {'times': (0, 'time_max'), 'voltages': ('mv_min', 'mv_max'), }
+        self.param_converters |= {'times': lambda x: [param_test_uint16(i) for i in x],
+                                  'voltages': lambda x: [abs_mv_to_dac(i) for i in x], }
+        self.defaults |= {'times': 0, 'voltages': 0}
+
+        if params:
+            self.progress_end = sum(self.params['times'])
+            self.params['n_steps'] = len(self.params['times'])
+        else:
+            self.progress_max = 0
+        self.progress_iters = 1
+        self.data_bytes = self.calculate_data_bytes()
 
-    def get_progress(self):
+    def get_progress(self) -> float:
+        if self.handler_instance.done:
+            return 100
         try:
-            return self.data['current_time'][-1][0][-1]/self.total_time
+            return (1 - abs(
+                self.progress_end - self.handler_instance.data['time_s'][-1]) / self.progress_end) * 100
         except IndexError:
             return 0
 
-class PDExp(Chronoamp):
-    """Photodiode/PMT experiment"""
-    id = 'pde'
-    def setup(self):
-        self.plots.append(ChronoampBox('current_time'))
-        
-        self.datatype = "linearData"
-        self.datalength = 2
-        self.databytes = 8
-        self.data = {'current_time' : [([],[])]}
-        self.columns = ['Time (s)', 'Current (A)']
-        self.plot_format = {
-                            'current_time' : {
-                                'labels' : self.columns,
-                                'xlims' : (0, int(self.parameters['time']))
-                                }
-                            }
-        self.total_time = int(self.parameters['time'])
-
-        if self.parameters['shutter_true']:
-            if self.parameters['sync_true']:
-                self.commands.append("EZ")
-                self.commands[-1] += str(self.parameters['sync_freq'])
-                self.commands[-1] += " "
-            else:
-                self.commands.append(("E2", []))
-
-        command = "ER1 "
-        params = []
-
-        if self.parameters['interlock_true']:
-            command += "1 "
-        else:
-            command += "0 "
-            
-        if self.parameters['voltage'] == 0: # Special case where V=0
-            params.append("65535")
-        else:
-            params.append(str(int(
-                            65535-(self.parameters['voltage']*(65536./3000))))
-                            )
-        params.append(str(self.parameters['time']))
-
-        self.commands.append((command, params))
 
-        if self.parameters['shutter_true']:
-            if self.parameters['sync_true']:
-                self.commands.append("Ez")
-            else:
-                self.commands.append("E1")
-                
-class FT_Box(PlotBox):
-    def updateline(self, Experiment, line_number):
-        def search_value(data, target):
-            for i in range(len(data)):
-                if data[i] > target:
-                    return i
-        
-        y = Experiment.data['data'][line_number][1]
-        x = Experiment.data['data'][line_number][0]
-        freq = Experiment.parameters['adc_rate_hz']
-        i = search_value(x, float(Experiment.parameters['fft_start']))
-        y1 = y[i:]
-        x1 = x[i:]
-        avg = mean(y1)
-        min_index, max_index = findBounds(y1)
-        y1[min_index] = avg
-        y1[max_index] = avg
-        f, Y = plotSpectrum(y1[min_index:max_index],freq)
-        self.axe1.lines[line_number].set_ydata(Y)
-        self.axe1.lines[line_number].set_xdata(f)
-        Experiment.data['ft'] = [(f, Y)]
-        
-    def changetype(self, Experiment):
-        """Change plot type. Set axis labels and x bounds to those stored
-        in the Experiment instance. Stores class instance in Experiment.
-        """
-        self.axe1.set_xlabel("Freq (Hz)")
-        self.axe1.set_ylabel("|Y| (A/Hz)")
-        self.axe1.set_xlim(0, Experiment.parameters['adc_rate_hz']/2)
-        
-        Experiment.plots['ft'] = self
 
-        self.figure.canvas.draw()
\ No newline at end of file
diff --git a/dstat_interface/core/experiments/cv.py b/dstat_interface/core/experiments/cv.py
deleted file mode 100644
index c08e610..0000000
--- a/dstat_interface/core/experiments/cv.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import time
-import struct
-
-from .experiment_template import PlotBox, Experiment
-
-
-class CVExp(Experiment):
-    id = 'cve'
-    """Cyclic Voltammetry experiment"""
-    def setup(self):
-        self.plotlims['current_voltage']['xlims'] = tuple(
-                sorted((int(self.parameters['v1']), int(self.parameters['v2'])))
-                )
-        super(CVExp, self).setup()
-
-        self.datatype = "CVData"
-        self.xlabel = "Voltage (mV)"
-        self.ylabel = "Current (A)"
-        self.datalength = 2 * self.parameters['scans']  # x and y for each scan
-        self.databytes = 6  # uint16 + int32
-        
-        self.commands += "E"
-        self.commands[2] += "C"
-        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'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['dep_mV'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['v1'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['v2'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['start'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(self.parameters['scans'])
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['slope'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)
-                                ))
-        self.commands[2] += " "
-
-    def get_progress(self):
-        return (len(self.data['current_voltage'])-1) / float(self.parameters['scans'])
diff --git a/dstat_interface/core/experiments/experiment_container.py b/dstat_interface/core/experiments/experiment_container.py
new file mode 100644
index 0000000..2e192bc
--- /dev/null
+++ b/dstat_interface/core/experiments/experiment_container.py
@@ -0,0 +1,153 @@
+import logging
+import struct
+from abc import ABC, abstractmethod, ABCMeta
+from typing import Type, Callable, Union, Any, List
+
+from ..dstat.comm import SerialConnection
+from ..dstat.experiment_handler import ExperimentHandler
+from ..dstat.experiment_process import BaseExperimentProcess
+from ..dstat.state import DStatState
+from ..dstat.boards import V1_2Board
+
+logger = logging.getLogger(__name__)
+
+state = DStatState()
+board = V1_2Board()
+
+
+class ExperimentContainer(ABC, metaclass=ABCMeta):
+    dstat_params = {'buffer_false': bool, 'adc_rate': str,
+                    'adc_pga': str, 'gain': int,
+                    'short_true': bool, 'mux_channel': int}
+
+    @abstractmethod
+    def __init__(self, params: dict[str, Any], mux: int = 1):
+        self.mux = mux
+
+        self.data_cols: Union[None, list[str]] = None
+        self.data_format: str = ''
+        self.cmd_str: list[str] = ["EA{buffer} {adc_rate:x} {adc_pga:x} ",
+                                   "EG{gain} {short_true:d} "]
+
+        self.adc_rates = {label: code for label, code
+                          in zip(board.adc_rate_labels, board.adc_rate_codes)}
+        self.adc_pga = {label: code for label, code
+                        in zip(board.adc_pga_labels, board.adc_pga_codes)}
+
+        self.param_input: dict[str, Union[Callable, List]] = {'buffer_false': bool,
+                                                              'adc_rate': list(self.adc_rates.keys()),
+                                                              'adc_pga': list(self.adc_pga.keys()),
+                                                              'gain': int,
+                                                              'short_true': bool,
+                                                              }
+        self.param_input_limits: dict[str, tuple[Union[str, int], Union[str, int]]] = {}
+        self.param_input_display_names: dict[str, str] = {}
+        self.param_tables: dict[str, list[str]] = {}
+
+        self.param_docs: dict[str, str] = {'buffer_false': 'Disable ADC buffer',
+                                           'adc_rate': 'ADC sample rate',
+                                           'adc_pga': 'ADC PGA value',
+                                           'gain': 'IV converter gain',
+                                           'short_true': 'Short RE and CE internally'}
+        self.param_converters: dict[str, Callable] = {}
+        self.defaults: dict = {'buffer_false': False, 'short_true': False, 'adc_rate': '60', 'adc_pga': '2',
+                               'gain': 3000}
+        self.params = params
+        self.progress_max = 0
+        self.progress_iters = 0
+        self.progress_scan = 0
+
+        self.handler_instance: Union[None, ExperimentHandler] = None
+
+        self.commands: List[str] = []
+        self.calculate_data_bytes()
+
+        if mux > 1:
+            self.cmd_str += ['EX{mux_channel} ']
+            self.param_input['mux_channel'] = list(range(mux))
+            self.param_docs['mux_channel'] = 'Multiplexer Channel'
+            self.defaults['mux_channel'] = 0
+
+    @property
+    @abstractmethod
+    def experiment_id(self) -> Type[str]:
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def display_name(self) -> Type[str]:
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def data_bytes(self) -> Type[ExperimentHandler]:
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def handler(self) -> Type[ExperimentHandler]:
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def process(self) -> Type[BaseExperimentProcess]:
+        raise NotImplementedError
+
+    @property
+    def plots(self) -> dict[str, dict[str, str]]:
+        return {'current': {'x': 'voltage', 'y': 'current'}}
+
+    def calculate_data_bytes(self) -> int:
+        struct_formats = ['H' if i.islower() else 'l' for i in self.data_format]
+        return struct.Struct(f'<{"".join(struct_formats)}').size
+
+    def add_missing_from_defaults(self):
+        params = self.params
+        self.params = self.defaults.copy()
+        self.params |= params
+
+        self.commands = []
+        for i in self.cmd_str:
+            try:
+                cmd, params = i
+                param_list = []
+                for param in params:
+                    param_list += self.params[param]
+
+                self.commands += [(cmd.format(**self.params), param_list)]
+            except ValueError:
+                self.commands += [i.format(**self.params)]
+
+    def parse_params(self):
+        self.params |= {key: self.param_converters[key](value) for key, value in self.params.items()
+                        if key in self.param_converters}
+
+        self.params['adc_rate'] = self.adc_rates[self.params['adc_rate']]
+        self.params['adc_pga'] = self.adc_pga[self.params['adc_pga']]
+        self.params['gain'] = state.board_instance.gain.index(self.params['gain'])
+
+        if self.params['buffer_false']:
+            self.params['buffer'] = '0'
+        else:
+            self.params['buffer'] = '2'
+        logger.info(self.params)
+
+    def get_proc(self) -> BaseExperimentProcess:
+        proc = self.process()
+        self.parse_params()
+
+        for i in self.cmd_str:
+            proc.parse_command_string(i, self.params)
+
+        proc.data_bytes = self.data_bytes
+        return proc
+
+    def start_handler(self, ser: SerialConnection):
+        self.handler_instance = self.handler(ser=ser, data_cols=self.data_cols, data_format=self.data_format)
+        self.handler_instance.adc_gain = state.board_instance.gain[self.params['gain']]
+        self.handler_instance.adc_pga = state.board_instance.adc_pga[
+            state.board_instance.adc_pga_codes.index(self.params['adc_pga'])]
+
+    @abstractmethod
+    def get_progress(self) -> float:
+        pass
diff --git a/dstat_interface/core/experiments/experiment_loops.py b/dstat_interface/core/experiments/experiment_loops.py
deleted file mode 100644
index a350de8..0000000
--- a/dstat_interface/core/experiments/experiment_loops.py
+++ /dev/null
@@ -1,201 +0,0 @@
-#!/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/>.
-
-from __future__ import division, absolute_import, print_function, unicode_literals
-
-from ..dstat import state
-
-import logging
-
-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)
-
-logger = logging.getLogger(__name__)
-
-
-class BaseLoop(GObject.GObject):
-    __gsignals__ = {
-        b'experiment_done': (GObject.SIGNAL_RUN_FIRST, None, tuple()),
-        b'progress_update': (GObject.SIGNAL_RUN_FIRST, None, (float,))
-    }
-
-    def __init__(self, experiment, callbacks=None):
-        GObject.GObject.__init__(self)
-        self.line = None
-        self.lastdataline = 0
-        self.current_exp = experiment
-        self.experiment_proc = None
-
-        for signal, cb in callbacks.items():
-            try:
-                self.connect(signal, cb)
-            except TypeError:
-                logger.warning("Invalid signal %s", signal)
-
-    def run(self):
-        self.experiment_proc = [
-            GObject.idle_add(self.experiment_running_data),
-            GObject.idle_add(self.experiment_running_proc),
-            GObject.timeout_add(100, self.update_progress)
-        ]
-
-    def experiment_running_data(self):
-        """Receive data from experiment process and add to
-        current_exp.data['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:
-            incoming = state.ser.get_data()
-            while incoming is not None:
-                try:
-                    self.line = incoming[0]
-                    if self.line > self.lastdataline:
-                        newline = True
-                        try:
-                            logger.info("running scan_process()")
-                            self.current_exp.scan_process(self.lastdataline)
-                        except AttributeError:
-                            pass
-                        self.lastdataline = self.line
-                    else:
-                        newline = False
-                    self.current_exp.store_data(incoming, newline)
-                except TypeError:
-                    pass
-
-                incoming = state.ser.get_data()
-            return True
-
-        except EOFError as err:
-            logger.error(err)
-            self.experiment_done()
-            return False
-        except IOError as err:
-            logger.error(err)
-            self.experiment_done()
-            return False
-
-    def experiment_running_proc(self):
-        """Receive proc signals from experiment process.
-        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:
-            ctrl_buffer = state.ser.get_ctrl()
-            try:
-                if ctrl_buffer is not None:
-                    self.current_exp.ctrl_loop(ctrl_buffer)
-            except AttributeError:
-                pass
-
-            proc_buffer = state.ser.get_proc()
-            if proc_buffer is not None:
-                if proc_buffer in ["DONE", "SERIAL_ERROR", "ABORT"]:
-                    self.experiment_done()
-                    if proc_buffer == "SERIAL_ERROR":
-                        self.on_serial_disconnect_clicked()
-
-                else:
-                    logger.warning("Unrecognized experiment return code: %s",
-                                   proc_buffer)
-                return False
-            return True
-
-        except EOFError as err:
-            logger.warning("EOFError: %s", err)
-            self.experiment_done()
-            return False
-        except IOError as err:
-            logger.warning("IOError: %s", err)
-            self.experiment_done()
-            return False
-
-    def experiment_done(self):
-        logger.info("Experiment done")
-        for proc in self.experiment_proc:
-            GObject.source_remove(proc)
-        self.current_exp.scan_process(self.lastdataline)
-        self.current_exp.experiment_done()
-        self.emit("experiment_done")
-
-    def update_progress(self):
-        try:
-            progress = self.current_exp.get_progress()
-        except AttributeError:
-            progress = -1
-        self.emit("progress_update", progress)
-        return True
-
-
-class PlotLoop(BaseLoop):
-    def experiment_running_plot(self, force_refresh=False):
-        """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 is None:
-            return True
-        for plot in self.current_exp.plots:
-            if (plot.scan_refresh and self.line > self.lastdataline):
-                while self.line > self.lastline:
-                    # make sure all of last line is added
-                    plot.updateline(self.current_exp, self.lastdataline)
-                    self.lastdataline += 1
-                plot.updateline(self.current_exp, self.line)
-                plot.redraw()
-            else:
-                while self.line > self.lastdataline:
-                    # make sure all of last line is added
-                    plot.updateline(self.current_exp, self.lastdataline)
-                    self.lastdataline += 1
-                plot.updateline(self.current_exp, self.line)
-
-            if plot.continuous_refresh is True or force_refresh is True:
-                plot.redraw()
-        return True
-
-    def run(self):
-        super(PlotLoop, self).run()
-        self.experiment_proc.append(
-            GObject.timeout_add(200, self.experiment_running_plot)
-        )
-
-    def experiment_done(self):
-        logger.info("Experiment done")
-        for proc in self.experiment_proc:
-            GObject.source_remove(proc)
-        self.current_exp.scan_process(self.lastdataline)
-        self.current_exp.experiment_done()
-        self.experiment_running_plot(force_refresh=True)
-        self.emit("experiment_done")
\ No newline at end of file
diff --git a/dstat_interface/core/experiments/experiment_template.py b/dstat_interface/core/experiments/experiment_template.py
deleted file mode 100755
index 05a17a1..0000000
--- a/dstat_interface/core/experiments/experiment_template.py
+++ /dev/null
@@ -1,571 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#     DStat Interface - An interface for the open hardware DStat potentiostat
-#     Copyright (C) 2017  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
-import struct
-import time
-from collections import OrderedDict
-from copy import deepcopy
-from datetime import datetime
-from math import ceil
-
-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)
-
-
-from matplotlib.figure import Figure
-import matplotlib.gridspec as gridspec
-
-from matplotlib.backends.backend_gtk3agg \
-    import FigureCanvasGTK3Agg as FigureCanvas
- 
-from pandas import DataFrame
-import seaborn as sns
-sns.set(context='paper', style='darkgrid')
-
-import serial
-
-from ..dstat import state, comm
-from ..dstat.comm import TransmitError
-from . import experiment_loops
-
-logger = logging.getLogger(__name__)
-dstat_logger = logging.getLogger("{}.DSTAT".format(comm.__name__))
-exp_logger = logging.getLogger("{}.Experiment".format(__name__))
-
-
-class Experiment(GObject.Object):
-    """Store and acquire a potentiostat experiment. Meant to be subclassed
-    to by different experiment types and not used instanced directly. Subclass
-    must instantiate self.plotbox as the PlotBox class to use and define id as
-    a class attribute.
-    """
-    id = None
-    Loops = experiment_loops.PlotLoop
-    __gsignals__ = {
-        b'exp_ready': (GObject.SIGNAL_RUN_FIRST, None, ()),
-        b'exp_done': (GObject.SIGNAL_RUN_FIRST, None, ()),
-    }
-
-    def __init__(self, parameters):
-        """Adds commands for gain and ADC."""
-        super(Experiment, self).__init__()
-        self.current_command = None
-        self.parameters = parameters
-        self.databytes = 8
-        self.datapoint = 0
-        self.scan = 0
-        self.time = 0
-        self.plots = []
-        
-        self.re_voltage_scale = state.board_instance.re_voltage_scale
-
-        self.gain = state.board_instance.gain[int(self.parameters['gain'])]
-
-        try:
-            self.gain_trim = int(
-                state.settings[
-                    state.board_instance.gain_trim[int(self.parameters['gain'])]
-                ][1]
-            )
-        except AttributeError:
-            logger.debug("No gain trim table.")
-
-        self.commands = ["EA", "EG"]
-        
-        if self.parameters['buffer_true']:
-            self.commands[0] += "2"
-        else:
-            self.commands[0] += "0"
-        self.commands[0] += " {p[adc_rate]} {p[adc_pga]} ".format(
-            p=self.parameters)
-        self.commands[1] += "{p[gain]} {p[short_true]:d} ".format(
-            p=self.parameters)
-
-        self.plotlims = {'current_voltage' : {'xlims' : (0, 1)}
-                        }
-
-        self.setup()
-        self.time = [datetime.utcnow()]
-
-    def setup_loops(self, callbacks):
-        self.loops = self.__class__.Loops(self, callbacks)
-        self.loops.run()
-
-    def setup(self):
-        self.data = OrderedDict(current_voltage=[([], [])])
-        self.columns = ['Voltage (mV)', 'Current (A)']
-
-        # list of scans, tuple of dimensions, list of data
-        self.line_data = ([], [])
-
-        plot = PlotBox(['current_voltage'])
-        plot.setlims('current_voltage', **self.plotlims['current_voltage'])
-
-        self.plots.append(plot)
-        
-    def write_command(self, cmd, params=None, retry=5):
-        """Write command to serial with optional number of retries."""
-        def get_reply(retries=3):
-            while True:
-                reply = self.serial.readline().rstrip()
-                if reply.startswith('#'):
-                    dstat_logger.info(reply)
-                elif reply == "":
-                    retries -= 1
-                    if retries <= 0:
-                        raise TransmitError
-                else:
-                    return reply
-        
-        n = len(cmd)
-        if params is not None:
-            n_params = len(params)
-            
-        for _ in range(retry):
-            tries = 5
-            while True:
-                time.sleep(0.2)
-                self.serial.reset_input_buffer()
-                self.serial.write('!{}\n'.format(n))
-                time.sleep(.1)
-                
-                try:
-                    reply = get_reply()
-                except TransmitError:
-                    if tries <= 0:
-                        continue
-                    tries -= 1
-                    pass
-                else:
-                    break
-        
-            if reply != "@ACK {}".format(n):
-                logger.warning("Expected ACK got: {}".format(reply))
-                continue
-            
-            tries = 5
-            
-            while True:
-                self.serial.write('{}\n'.format(cmd))
-                try:
-                    reply = get_reply()
-                except TransmitError:
-                    if tries <= 0:
-                        continue
-                    tries -= 1
-                    pass
-                else:
-                    break
-        
-            if reply != "@RCV {}".format(n):
-                logger.warning("Expected RCV got: {}".format(reply))
-                continue
-
-            if params is None:
-                return True
-            
-            tries = 5
-            
-            while True:
-                try:
-                    reply = get_reply()
-                except TransmitError:
-                    if tries <= 0:
-                        break
-                    tries -= 1
-                    pass
-                else:
-                    break
-                
-            if reply != "@RQP {}".format(n_params):
-                logger.warning("Expected RQP got: {}".format(reply))
-                continue
-            
-            tries = 5
-            
-            for i in params:
-                while True:
-                    self.serial.write(i + " ")
-                    try:
-                        reply = get_reply()
-                        if reply == "@RCVC {}".format(i):
-                            break
-                    except TransmitError:
-                        if tries <= 0:
-                            continue
-                        tries -= 1
-                        pass
-                    else:
-                        break
-            return True
-        return False
-
-    def run(self, ser, ctrl_pipe, data_pipe):
-        """Execute experiment. Connects and sends handshake signal to DStat
-        then sends self.commands.
-        """
-        self.serial = ser
-        self.ctrl_pipe = ctrl_pipe
-        self.data_pipe = data_pipe
-        
-        exp_logger.info("Experiment running")
-        
-        try:
-            for i in self.commands:
-                self.current_command = i
-                status = "DONE"
-                if isinstance(i, (str, unicode)):
-                    logger.info("Command: %s", i)
-                
-                    if not self.write_command(i):
-                        status = "ABORT"
-                        break
-            
-                else:
-                    cmd = i[0]
-                    data = i[1]
-                
-                    logger.info("Command: {}".format(cmd))
-                
-                    if not self.write_command(cmd, params=data):
-                        status = "ABORT"
-                        break
-                
-                if not self.serial_handler():
-                    status = "ABORT"
-                    break
-                
-                time.sleep(0.5)
-                    
-        except serial.SerialException:
-            status = "SERIAL_ERROR"
-        finally:
-            while self.ctrl_pipe.poll():
-                self.ctrl_pipe.recv()
-        return status
-    
-    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.data_pipe as result of self.data_handler).
-        """
-        scan = 0
-
-        def check_ctrl():
-            if self.ctrl_pipe.poll():
-                input = self.ctrl_pipe.recv()
-                logger.info("serial_handler: %s", input)
-                if input == "DISCONNECT":
-                    self.serial.write('a')
-                    self.serial.reset_input_buffer()
-                    logger.info("serial_handler: ABORT pressed!")
-                    time.sleep(.3)
-                    return False
-                elif input == 'a':
-                    self.serial.write('a')
-                else:
-                    self.serial.write(input)
-
-        try:
-            while True:
-                check_ctrl()
-                for line in self.serial:
-                    check_ctrl()
-                            
-                    if line.startswith('B'):
-                        data = self.data_handler(
-                                (scan, self.serial.read(size=self.databytes)))
-                        data = self.data_postprocessing(data)
-                        if data is not None:
-                            self.data_pipe.send(data)
-                        try:
-                            self.datapoint += 1
-                        except AttributeError: #Datapoint counting is optional
-                            pass
-                        
-                    elif line.lstrip().startswith('S'):
-                        scan += 1
-                        
-                    elif line.lstrip().startswith("#"):
-                        dstat_logger.info(line.lstrip().rstrip())
-                                        
-                    elif line.lstrip().startswith("@DONE"):
-                        dstat_logger.debug(line.lstrip().rstrip())
-                        time.sleep(.3)
-                        return True
-                        
-        except serial.SerialException:
-            return False
-
-    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)*self.re_voltage_scale,
-                       (current+self.gain_trim)*(1.5/8388607)/self.gain
-                       )
-               )
-    
-    def store_data(self, incoming, newline):
-        """Stores data in data attribute. Should not be called from subprocess.
-        Can be overriden for custom experiments."""
-        line, data = incoming
-        
-        if newline is True:
-            self.data['current_voltage'].append(deepcopy(self.line_data))
-
-        for i, item in enumerate(self.data['current_voltage'][line]):
-            item.append(data[i])
-        
-    def data_postprocessing(self, data):
-        """Discards first data point (usually gitched) by default, can be overridden
-        in subclass.
-        """
-        try:
-            if self.datapoint <= 1:
-                return None
-        except AttributeError: # Datapoint counting is optional
-            pass
-             
-        return data
-    
-    def scan_process(self, line):
-        pass
-    
-    def experiment_done(self):
-        """Runs when experiment is finished (all data acquired)"""
-        self.data_to_pandas()
-        self.time += [datetime.utcnow()]
-    
-    def export(self):
-        """Return a dict containing data for saving."""
-        output = {
-                  "datatype" : self.datatype,
-                  "xlabel" : self.xlabel,
-                  "ylabel" : self.ylabel,
-                  "xmin" : self.xmin,
-                  "xmax" : self.xmax,
-                  "parameters" : self.parameters,
-                  "data" : self.data,
-                  "commands" : self.commands
-                  }
-        
-        return output
-
-    def data_to_pandas(self):
-        """Convert data to pandas DataFrame and set as member of .df
-        attribute."""
-        self.df = OrderedDict()
-        
-        for name, data in self.data.items():
-            try:
-                df = DataFrame(
-                    columns=['Scan'] + list(self.plot_format[name]['labels']))
-            
-                for n, line in enumerate(data):  # Each scan
-                    df = df.append(
-                        DataFrame(
-                            OrderedDict(zip(
-                                ['Scan'] + list(self.plot_format[name]['labels']),
-                                    [n] + list(line))
-                            )
-                        ), ignore_index = True
-                    )
-            except (AttributeError, KeyError):
-                try:
-                    df = DataFrame(
-                        columns=['Scan'] + list(self.columns))
-            
-                    for n, line in enumerate(data):  # Each scan
-                        df = df.append(
-                            DataFrame(
-                                OrderedDict(zip(
-                                    ['Scan'] + list(self.columns),
-                                        [n] + list(line))
-                                )
-                            ), ignore_index = True
-                        )
-                except AttributeError as e:  # Fallback if no self.columns
-                    df = DataFrame(
-                        columns=['Scan'] + ["{}{}".format(name, n)
-                                            for n in range(len(data))]
-                    )
-            
-                    for n, line in enumerate(data):
-                        df = df.append(
-                            DataFrame(
-                                OrderedDict(zip(
-                                    ['Scan'] + ["{}{}".format(name, n)
-                                            for n in range(len(data))],
-                                        [n] + list(line))
-                                )
-                            ), ignore_index = True
-                        )
-            
-            self.df[name] = df
-            
-    def get_info_text(self):
-        """Return string of text to disply on Info tab."""
-        buf = "#Time: S{} E{}\n".format(self.time[0], self.time[1])
-        buf += "#Commands:\n"
-        
-        for line in self.commands:
-            buf += '#{}\n'.format(line)
-            
-        return buf
-    
-    def get_save_strings(self):
-        """Return dict of strings with experiment parameters and data."""
-        buf = {}
-        buf['params'] = self.get_info_text()
-        buf.update(
-            {exp : df.to_csv(sep='\t', encoding='utf-8')
-             for exp, df in self.df.items()}
-            )
-        
-        return buf
-
-
-class PlotBox(object):
-    """Contains data plot and associated methods."""
-    def __init__(self, plots=None):
-        """Initializes plots. self.box should be reparented."""
-        self.name = "Main"
-        self.continuous_refresh = True
-        self.scan_refresh = False
-
-        if plots is not None:
-            self.plotnames = plots
-        else:
-            self.plotnames = []
-        self.subplots = {}
-        
-        self.figure = Figure()
-        # self.figure.subplots_adjust(left=0.07, bottom=0.07,
-        #                             right=0.96, top=0.96)
-
-        self.setup()
-        self.format_plots()  # Should be overriden by subclass
-        
-        self.figure.set_tight_layout(True)
-        
-        self.canvas = FigureCanvas(self.figure)
-        self.canvas.set_vexpand(True)
-        
-        self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
-        self.box.pack_start(self.canvas, expand=True, fill=True, padding=0)
-
-    def setup(self):
-        self.plot_format = {
-            'current_voltage': {'xlabel': "Voltage (mV)",
-                                'ylabel': "Current (A)"
-                                }
-        }
-
-    def format_plots(self):
-        """
-        Creates and formats subplots needed. Should be overriden by subclass
-        """
-        # Calculate size of grid needed
-        
-        if len(self.plotnames) > 1:
-            gs = gridspec.GridSpec(int(ceil(len(self.plotnames)/2.)),2)
-        else:
-            gs = gridspec.GridSpec(1,1)
-        for n, i in enumerate(self.plotnames):
-            self.subplots[i] = self.figure.add_subplot(gs[n])
-        
-        for key, subplot in self.subplots.items():
-            subplot.ticklabel_format(style='sci', scilimits=(0, 3),
-                                     useOffset=False, axis='y')
-            subplot.plot([], [])
-            subplot.set_xlabel(self.plot_format[key]['xlabel'])
-            subplot.set_ylabel(self.plot_format[key]['ylabel'])
-        
-    def clearall(self):
-        """Remove all lines on plot. """
-        for name, plot in self.subplots.items():
-            for line in reversed(plot.lines):
-                line.remove()
-        self.addline()
-    
-    def clearline(self, subplot, line_number):
-        """Remove a single line.
-        
-        Arguments:
-        subplot -- key in self.subplots
-        line_number -- line number in subplot
-        """
-        self.subplots[subplot].lines[line_number].remove()
-        
-    def addline(self):
-        """Add a new line to plot. (initialized with dummy data)))"""
-        for subplot in self.subplots.values():
-            subplot.plot([], [])
-    
-    def updateline(self, Experiment, line_number):
-        """Update a line specified with new data.
-        
-        Arguments:
-        Experiment -- Experiment instance
-        line_number -- line number to update
-        """
-        for subplot in Experiment.data:
-            while True:
-                try:
-                    self.subplots[subplot].lines[line_number].set_xdata(
-                        Experiment.data[subplot][line_number][0])
-                    self.subplots[subplot].lines[line_number].set_ydata(
-                        Experiment.data[subplot][line_number][1])
-                except IndexError:
-                    self.addline()
-                except KeyError:
-                    pass
-                else:
-                    break
-                    # logger.warning("Tried to set line %s that doesn't exist.", line_number)
-
-    def setlims(self, plot, xlims=None, ylims=None):
-        """Sets x and y limits.
-        """
-        if xlims is not None:
-            self.subplots[plot].set_xlim(xlims)
-        if ylims is not None:
-            self.subplots[plot].set_ylim(ylims)
-        
-        self.figure.canvas.draw()
-        
-    def redraw(self):
-        """Autoscale and refresh the plot."""
-        for name, plot in self.subplots.items():
-            plot.relim()
-            plot.autoscale(True, axis = 'y')
-        self.figure.canvas.draw()
-
-        return True
\ No newline at end of file
diff --git a/dstat_interface/core/experiments/idle.py b/dstat_interface/core/experiments/idle.py
deleted file mode 100644
index b8caf4d..0000000
--- a/dstat_interface/core/experiments/idle.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import time
-import struct
-
-from .experiment_template import Experiment
-from ..dstat import state
-
-class OCPExp(Experiment):
-    """Open circuit potential measumement in statusbar."""
-    id = 'ocp'
-
-    def __init__(self):
-        self.re_voltage_scale = state.board_instance.re_voltage_scale
-        self.databytes = 8
-        
-        self.commands = ["EA", "EP0 0 "]
-    
-        self.commands[0] += "2 " # input buffer
-        self.commands[0] += "3 " # 2.5 Hz sample rate
-        self.commands[0] += "1 " # 2x PGA
-
-    def data_handler(self, data_input):
-        """Overrides Experiment method to only send ADC values."""
-        scan, data = data_input
-        # 2*uint16 + int32
-        seconds, milliseconds, voltage = struct.unpack('<HHl', data)
-        return voltage/5.592405e6*self.re_voltage_scale
-
-
-class PMTIdle(Experiment):
-    """PMT idle mode."""
-    id = "pmt_idle"
-
-    def __init__(self):
-        self.databytes = 8
-    
-        self.commands = ["EA", "EM"]
-    
-        self.commands[0] += "2 " # input buffer
-        self.commands[0] += "3 " # 2.5 Hz sample rate
-        self.commands[0] += "1 " # 2x PGA
diff --git a/dstat_interface/core/experiments/lsv.py b/dstat_interface/core/experiments/lsv.py
index ce2a2e1..68b5091 100644
--- a/dstat_interface/core/experiments/lsv.py
+++ b/dstat_interface/core/experiments/lsv.py
@@ -1,67 +1,123 @@
-import time
-import struct
-
-from .experiment_template import PlotBox, Experiment
-                                     
-class LSVExp(Experiment):
-    """Linear Scan Voltammetry experiment"""
-    id = 'lsv'
-    def setup(self):
-        self.plotlims['current_voltage']['xlims'] = tuple(
-                    sorted(
-                        (int(self.parameters['start']),
-                         int(self.parameters['stop']))
-                           )
-                )
-
-        super(LSVExp, self).setup()
-        
-        self.datatype = "linearData"
-        self.datalength = 2
-        self.databytes = 6  # uint16 + int32
-
-        self.stop_mv = int(self.parameters['stop'])
-        self.max_mv = abs(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'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['dep_mV'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['start'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['stop'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['slope'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)
-                                ))
-        self.commands[2] += " "
+from typing import Any
+
+from ..dstat.experiment_handler import ExperimentHandler
+from ..dstat.experiment_process import ExperimentProcess
+from ..dstat.utility import abs_mv_to_dac, rel_mv_to_dac, param_test_uint16, param_test_non_zero_uint8
+from ..experiments.experiment_container import ExperimentContainer
+
+
+class LSVExperimentContainer(ExperimentContainer):
+    experiment_id = 'lsv'
+    display_name = 'Linear Scan Voltammetry'
+    process = ExperimentProcess
+    handler = ExperimentHandler
+    data_bytes = 0
+
+    def __init__(self, params: dict[str, Any], mux: int = 1):
+        super().__init__(params, mux=mux)
+
+        self.data_cols = ['voltage', 'current']
+        self.data_format = 'vA'
+
+        self.cmd_str += ['EL{clean_s} {dep_s} {clean_mv} {dep_mv} {start} {stop} {slope} ']
+        self.param_input |= {'clean_s': int, 'dep_s': int, 'clean_mv': int, 'dep_mv': int,
+                             'start': int, 'stop': int, 'slope': int}
+        self.param_input_display_names |= {'clean_s': 't_clean (s)', 'dep_s': 't_dep (s)',
+                                           'clean_mv': 'V_clean (mV)', 'dep_mv': 'V_dep (mV)',
+                                           'start': 'V_start (mV)', 'stop': 'V_stop (mV)', 'slope': 'Slope (mV/s)', }
+        self.param_input_limits |= {'clean_s': (0, 'time_max'), 'dep_s': (0, 'time_max'),
+                                    'clean_mv': ('mv_min', 'mv_max'), 'dep_mv': ('mv_min', 'mv_max'),
+                                    'start': ('mv_min', 'mv_max'), 'stop': ('mv_min', 'mv_max'),
+                                    'slope': (1, 5000)}
+        self.param_converters |= {'clean_s': param_test_uint16, 'dep_s': param_test_uint16,
+                                  'clean_mv': abs_mv_to_dac, 'dep_mv': abs_mv_to_dac,
+                                  'start': abs_mv_to_dac, 'stop': abs_mv_to_dac, 'slope': rel_mv_to_dac}
+        self.defaults |= {'clean_s': 0, 'dep_s': 0, 'clean_mv': 0, 'dep_mv': 0}
+        if params:
+            self.progress_max = abs(self.params['stop'] - self.params['start'])
+            self.progress_end = self.params['stop']
+        else:
+            self.progress_max = 0
+        self.progress_iters = 1
+        self.data_bytes = self.calculate_data_bytes()
+
+    def get_progress(self) -> float:
+        if self.handler_instance.done:
+            return 100
+        try:
+            return (1 - abs(self.progress_end - self.handler_instance.data['voltage'][-1]) / self.progress_max) * 100
+        except IndexError:
+            return 0
+
+
+class CVExperimentContainer(ExperimentContainer):
+    experiment_id = 'cv'
+    display_name = 'Cyclic Voltammetry'
+    process = ExperimentProcess
+    handler = ExperimentHandler
+    plots = {'current': {'x': 'voltage', 'y': 'current', 'hue': 'scan'}}
+    data_bytes = 0
+
+    def __init__(self, params: dict[str, Any], mux: int = 1):
+        super().__init__(params, mux=mux)
+
+        self.data_cols = ['voltage', 'current']
+        self.data_format = 'vA'
+
+        self.cmd_str += ['EC{clean_s} {dep_s} {clean_mv} {dep_mv} {v1} {v2} {start} {scans} {slope} ']
+        self.param_input |= {'clean_s': int, 'dep_s': int, 'clean_mv': int, 'dep_mv': int, 'v1': int, 'v2': int,
+                             'start': int, 'scans': int, 'slope': int}
+        self.param_input_display_names |= {'clean_s': 't_clean (s)', 'dep_s': 't_dep (s)',
+                                           'clean_mv': 'V_clean (mV)', 'dep_mv': 'V_dep (mV)',
+                                           'start': 'V_start (mV)', 'v1': 'V_1 (mV)', 'v2': 'V_2 (mV)',
+                                           'scans': 'Scans', 'slope': 'Slope (mV/s)'}
+        self.param_input_limits |= {'clean_s': (0, 'time_max'), 'dep_s': (0, 'time_max'),
+                                    'clean_mv': ('mv_min', 'mv_max'), 'dep_mv': ('mv_min', 'mv_max'),
+                                    'start': ('mv_min', 'mv_max'), 'v1': ('mv_min', 'mv_max'),
+                                    'v2': ('mv_min', 'mv_max'),
+                                    'scans': (1, 'scans_max'), 'slope': (1, 5000)}
+        self.param_converters |= {'clean_s': param_test_uint16, 'dep_s': param_test_uint16,
+                                  'clean_mv': abs_mv_to_dac, 'dep_mv': abs_mv_to_dac,
+                                  'v1': abs_mv_to_dac, 'v2': abs_mv_to_dac, 'start': abs_mv_to_dac,
+                                  'scans': param_test_non_zero_uint8,
+                                  'slope': rel_mv_to_dac}
+        self.defaults |= {'clean_s': 0, 'dep_s': 0, 'clean_mv': 0, 'dep_mv': 0}
+        if params:
+            self.progress_max = 2 * abs(self.params['v1'] - self.params['v2'])
+            self.progress_start = self.params['start']
+            self.progress_v1 = self.params['v1']
+            self.progress_v2 = self.params['v2']
+            self.progress_iters = self.params['scans']
+            self.progress_scan = 0
+            self.progress_lastmv = self.params['start']
+        else:
+            self.progress_max = 0
+
+        self.data_bytes = self.calculate_data_bytes()
 
     def get_progress(self):
         try:
-            return 1 - (abs(self.stop_mv - self.data['current_voltage'][-1][0][-1])/self.max_mv)
+            if self.handler_instance.done:
+                return 100
+
+            if self.handler_instance.data['scan'][-1] > self.progress_scan:
+                self.progress_scan = self.handler_instance.data['scan'][-1]
+                self.progress_lastmv = self.progress_start
+                raise StopIteration
+
+            current_mv = self.handler_instance.data['voltage'][-1]
+
+            # if moving towards v1
+            if abs(self.progress_v1 - self.progress_lastmv) > abs(self.progress_v1 - current_mv):
+                # Between v2 and start v1-mv and start-mv have same sign
+                if (self.progress_v1 - current_mv > 0) == (self.progress_start - current_mv > 0):
+                    progress = 100 * (1 - abs(self.progress_start - current_mv) / self.progress_max)
+                else:
+                    progress = 100 * (.25 - abs(self.progress_v1 - current_mv) / self.progress_max)
+            else:
+                progress = 100 * (.75 - abs(self.progress_v2 - current_mv) / self.progress_max)
+
+            self.progress_lastmv = current_mv
+            return progress
         except IndexError:
-            return 0
\ No newline at end of file
+            return 0
diff --git a/dstat_interface/core/experiments/pot.py b/dstat_interface/core/experiments/pot.py
deleted file mode 100644
index 0f5971c..0000000
--- a/dstat_interface/core/experiments/pot.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import time
-import struct
-
-from .experiment_template import PlotBox, Experiment
-
-
-class PotBox(PlotBox):
-    def setup(self):
-        self.plot_format = {
-            'voltage_time': {'xlabel': "Time (s)",
-                             'ylabel': "Voltage (V)"
-                            }
-        }
-
-    def format_plots(self):
-        """
-        Creates and formats subplots needed. Overrides superclass.
-        """
-        self.subplots = {'voltage_time': self.figure.add_subplot(111)}
-        
-        for key, subplot in self.subplots.items():
-            subplot.ticklabel_format(style='sci', scilimits=(0, 3),
-                                     useOffset=False, axis='y')
-            subplot.plot([],[])
-            subplot.set_xlabel(self.plot_format[key]['xlabel'])
-            subplot.set_ylabel(self.plot_format[key]['ylabel'])
-
-
-class PotExp(Experiment):
-    id = 'pot'
-    """Potentiometry experiment"""
-    def setup(self):
-        self.plots.append(PotBox(['voltage_time']))
-        
-        self.datatype = "linearData"
-        self.datalength = 2
-        self.databytes = 8
-        self.data = {'voltage_time' : [([],[])]}
-        self.columns = ['Time (s)', 'Voltage (V)']
-        self.plotlims = {
-            'voltage_time': {
-                'xlims': (0, int(self.parameters['time']))
-            }
-        }
-        self.plots[-1].setlims('voltage_time', **self.plotlims['voltage_time'])
-
-        self.total_time = int(self.parameters['time'])
-
-        self.commands += "E"
-        self.commands[2] += "P"
-        self.commands[2] += str(self.parameters['time'])
-        self.commands[2] += " 1 "  # potentiometry mode
-
-    def data_handler(self, data_input):
-        """Overrides Experiment method to not convert x axis to mV."""
-        scan, data = data_input
-        # 2*uint16 + int32
-        seconds, milliseconds, voltage = struct.unpack('<HHl', data)
-        return (scan, (
-                       seconds+milliseconds/1000.,
-                       voltage*self.re_voltage_scale*(1.5/8388607.)
-                       )
-                )
-
-    def store_data(self, incoming, newline):
-        """Stores data in data attribute. Should not be called from subprocess.
-        Can be overriden for custom experiments."""
-        line, data = incoming
-        
-        if newline is True:
-            self.data['voltage_time'].append(deepcopy(self.line_data))
-
-        for i, item in enumerate(self.data['voltage_time'][line]):
-            item.append(data[i])
-
-    def get_progress(self):
-        try:
-            return self.data['voltage_time'][-1][0][-1]/self.total_time
-        except IndexError:
-            return 0
\ No newline at end of file
diff --git a/dstat_interface/core/experiments/swv.py b/dstat_interface/core/experiments/swv.py
index d60c447..11c2d8c 100644
--- a/dstat_interface/core/experiments/swv.py
+++ b/dstat_interface/core/experiments/swv.py
@@ -1,236 +1,155 @@
-import time
-import struct
-from copy import deepcopy
-
-from .experiment_template import PlotBox, Experiment
-
-
-class SWVBox(PlotBox):
-    def setup(self):
-        self.plot_format = {
-            'swv': {'xlabel': "Voltage (mV)",
-                    'ylabel': "Current (A)"
-                    }
-        }
-    def format_plots(self):
-        """
-        Creates and formats subplots needed. Overrides superclass.
-        """
-        self.subplots = {'swv': self.figure.add_subplot(111)}
-        
-        for key, subplot in self.subplots.items():
-            subplot.ticklabel_format(style='sci', scilimits=(0, 3),
-                                     useOffset=False, axis='y')
-            subplot.plot([],[])
-            subplot.set_xlabel(self.plot_format[key]['xlabel'])
-            subplot.set_ylabel(self.plot_format[key]['ylabel'])
-
-
-class SWVExp(Experiment):
-    """Square Wave Voltammetry experiment"""
-    id = 'swv'
-
-    def setup(self):
-        self.datatype = "SWVData"
-        self.xlabel = "Voltage (mV)"
-        self.ylabel = "Current (A)"
-        self.data = {
-                     'swv' : [([], [], [], [])]
-                    }  # voltage, current, forwards, reverse
-        self.line_data = ([], [], [], [])
-        self.datalength = 2 * self.parameters['scans']
-        self.databytes = 10
-        self.columns = ['Voltage (mV)', 'Net Current (A)',
-                        'Forward Current (A)', 'Reverse Current (A)']
-        self.plotlims = {
-            'swv': {
-                'xlims': tuple(sorted(
-                    (int(self.parameters['start']),
-                     int(self.parameters['stop']))
-                )
-                )
-            }
-        }
-
-        plot = SWVBox()
-        plot.setlims('swv', **self.plotlims['swv'])
-        self.plots.append(plot)
-
-        self.stop_mv = int(self.parameters['stop'])
-        self.max_mv = abs(int(self.parameters['start']) - int(self.parameters['stop']))
-        self.scan_points = self.max_mv * 2 / float(self.parameters['step'])
-
-        self.commands += "E"
-        self.commands[2] += "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(
-                                    int(self.parameters['clean_mV'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['dep_mV'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['start'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['stop'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['step'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['pulse'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(self.parameters['freq'])
-        self.commands[2] += " "
-        self.commands[2] += str(self.parameters['scans'])
-        self.commands[2] += " "
-    
-    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)
-        f_trim = forward+self.gain_trim
-        r_trim = reverse+self.gain_trim
-        
-        return (scan, (
-                       (voltage-32768)*3000./65536*self.re_voltage_scale,
-                       (f_trim-r_trim)*(1.5/self.gain/8388607),
-                       f_trim*(1.5/self.gain/8388607),
-                       r_trim*(1.5/self.gain/8388607)
-                       )
-                )
-    
-    def store_data(self, incoming, newline):
-        """Stores data in data attribute. Should not be called from subprocess.
-        Can be overriden for custom experiments."""
-        line, data = incoming
-        
-        if newline is True:
-            self.data['swv'].append(deepcopy(self.line_data))
-
-        for i, item in enumerate(self.data['swv'][line]):
-            item.append(data[i])
+from datetime import datetime
+from typing import Any, Tuple
+
+from ..dstat.experiment_handler import ExperimentHandler
+from ..dstat.experiment_process import ExperimentProcess
+from ..dstat.utility import abs_mv_to_dac, rel_mv_to_dac, param_test_non_zero_uint16, \
+    param_test_uint16, param_test_uint8
+from ..experiments.experiment_container import ExperimentContainer
+
+
+class SWVExperimentHandler(ExperimentHandler):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.data['current'] = []
+
+    def data_handler(self, data_input: Tuple[datetime, int, bytes]):
+        unpacked: Tuple[int, int, int]
+        date, scan, data = data_input
+        unpacked = self.struct.unpack(data)
+        self.data['scan'].append(scan)
+        self.data['timestamp'].append(date)
+
+        for n, i in enumerate(unpacked):
+            try:
+                self.data[self.data_cols[n]].append(self.data_convert[n](i))
+            except TypeError:  # If no converter
+                self.data[self.data_cols[n]].append(i)
+        self.data['current'].append(self.adc_to_amps(unpacked[1] - unpacked[2]))  # forward - reverse
+
+
+class SWVExperimentContainer(ExperimentContainer):
+    experiment_id = 'swv'
+    display_name = 'Square Wave Voltammetry'
+    process = ExperimentProcess
+    handler = SWVExperimentHandler
+    plots = {'current': {'x': 'voltage', 'y': 'current', 'hue': 'scan'}}
+    data_bytes = 0
+
+    def __init__(self, params: dict[str, Any], mux: int = 1):
+        super().__init__(params, mux=mux)
+
+        self.data_cols = ['voltage', 'forward', 'reverse']
+        self.data_format = 'vAA'
+
+        self.cmd_str += ['ES{clean_s} {dep_s} {clean_mv} {dep_mv} {start} {stop} {step} {pulse} {freq} {scans} ']
+        self.param_input |= {'clean_s': int, 'dep_s': int, 'clean_mv': int, 'dep_mv': int,
+                             'start': int, 'stop': int, 'step': int, 'pulse': int, 'freq': int,
+                             'scans': int}
+        self.param_input_display_names |= {'clean_s': 't_clean (s)', 'dep_s': 't_dep (s)',
+                                           'clean_mv': 'V_clean (mV)', 'dep_mv': 'V_dep (mV)',
+                                           'start': 'V_start (mV)', 'stop': 'V_stop (mV)', 'step': 'V_step (mV)',
+                                           'pulse': 'V_pulse (mV)', 'freq': 'f (Hz)',
+                                           'scans': 'Scans'}
+        self.param_input_limits |= {'clean_s': (0, 'time_max'), 'dep_s': (0, 'time_max'),
+                                    'clean_mv': ('mv_min', 'mv_max'), 'dep_mv': ('mv_min', 'mv_max'),
+                                    'start': ('mv_min', 'mv_max'), 'stop': ('mv_min', 'mv_max'),
+                                    'step': (1, 1000), 'pulse': (1, 1000), 'freq': (1, 'freq_max'),
+                                    'scans': (0, 'scans_max')}
+        self.param_converters |= {'clean_s': param_test_uint16, 'dep_s': param_test_uint16,
+                                  'clean_mv': abs_mv_to_dac, 'dep_mv': abs_mv_to_dac,
+                                  'start': abs_mv_to_dac, 'stop': abs_mv_to_dac, 'step': rel_mv_to_dac,
+                                  'pulse': rel_mv_to_dac, 'freq': param_test_non_zero_uint16,
+                                  'scans': param_test_uint8}
+        self.defaults |= {'clean_s': 0, 'dep_s': 0, 'clean_mv': 0, 'dep_mv': 0}
+        if params:
+            if self.params['scans'] > 0:
+                self.progress_max = 2 * abs(self.params['stop'] - self.params['start'])
+            else:
+                self.progress_max = abs(self.params['stop'] - self.params['start'])
+            self.progress_start = self.params['start']
+            self.progress_stop = self.params['stop']
+            self.progress_iters = self.params['scans']
+            self.progress_scan = 0
+            self.progress_lastmv = self.params['start']
+        else:
+            self.progress_max = 0
+        self.data_bytes = self.calculate_data_bytes()
 
     def get_progress(self):
+        if self.handler_instance.done:
+            return 100
         try:
-            if int(self.parameters['scans']) != 0:
-                scans_prog = (len(self.data['swv'])-1) / float(self.parameters['scans'])
-                scan_prog = (len(self.data['swv'][-1][0])-1) / self.scan_points / float(self.parameters['scans'])
-                prog = scans_prog + scan_prog
-                if prog > 1:
-                    prog = 1
-                return prog
+            if self.params['scans'] > 0:
+                if self.handler_instance.data['scan'][-1] > self.progress_scan:
+                    self.progress_scan = self.handler_instance.data['scan'][-1]
+                    self.progress_lastmv = self.progress_start
+                    raise StopIteration
+
+                current_mv = self.handler_instance.data['voltage'][-1]
+
+                if abs(self.progress_stop - self.progress_lastmv) > abs(self.progress_stop - current_mv):
+                    progress = 100 * (.5 - abs(self.progress_stop - current_mv) / self.progress_max)
+                else:
+                    progress = 100 * (1 - abs(self.progress_start - current_mv) / self.progress_max)
+
+                self.progress_lastmv = current_mv
+                return progress
             else:
-                return 1 - (abs(self.stop_mv - self.data['swv'][-1][0][-1])/self.max_mv)
+                return (1 - abs(
+                    self.progress_stop - self.handler_instance.data['voltage'][-1]) / self.progress_max) * 100
         except IndexError:
             return 0
 
 
-class DPVExp(SWVExp):
-    """Diffential Pulse Voltammetry experiment."""
-    id = 'dpv'
-    def setup(self):
-        self.datatype = "SWVData"
-        self.xlabel = "Voltage (mV)"
-        self.ylabel = "Current (A)"
-        self.data = {
-                     'swv' : [([], [], [], [])]
-                    }  # voltage, current, forwards, reverse
-        self.line_data = ([], [], [], [])
-        self.datalength = 2
-        self.databytes = 10
-        self.columns = ['Voltage (mV)', 'Net Current (A)',
-                        'Forward Current (A)', 'Reverse Current (A)']
-
-        self.plotlims = {
-            'swv': {
-                'xlims': tuple(sorted(
-                    (int(self.parameters['start']),
-                     int(self.parameters['stop']))
-                )
-                )
-            }
-        }
-
-        plot = SWVBox()
-        plot.setlims('swv', **self.plotlims['swv'])
-        self.plots.append(plot)
-
-        self.stop_mv = int(self.parameters['stop'])
-        self.max_mv = abs(int(self.parameters['start']) - int(self.parameters['stop']))
-
-        self.commands += "E"
-        self.commands[2] += "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(
-                                    int(self.parameters['clean_mV'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['dep_mV'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['start'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['stop'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)+32768
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['step'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(int(
-                                    int(self.parameters['pulse'])/
-                                    self.re_voltage_scale*
-                                    (65536./3000)
-                                ))
-        self.commands[2] += " "
-        self.commands[2] += str(self.parameters['period'])
-        self.commands[2] += " "
-        self.commands[2] += str(self.parameters['width'])
-        self.commands[2] += " "
+class DPVExperimentContainer(ExperimentContainer):
+    experiment_id = 'dpv'
+    display_name = 'Differential Pulse Voltammetry'
+    process = ExperimentProcess
+    handler = SWVExperimentHandler
+    data_bytes = 0
+
+    def __init__(self, params: dict[str, Any], mux: int = 1):
+        super().__init__(params, mux=mux)
+
+        self.data_cols = ['voltage', 'forward', 'reverse']
+        self.data_format = 'vAA'
+
+        self.cmd_str += ['ED{clean_s} {dep_s} {clean_mv} {dep_mv} {start} {stop} {step} {pulse} {period} {width} ']
+        self.param_input |= {'clean_s': int, 'dep_s': int, 'clean_mv': int, 'dep_mv': int,
+                             'start': int, 'stop': int, 'step': int, 'pulse': int, 'period': int, 'width': int}
+        self.param_input_display_names |= {'clean_s': 't_clean (s)', 'dep_s': 't_dep (s)',
+                                           'clean_mv': 'V_clean (mV)', 'dep_mv': 'V_dep (mV)',
+                                           'start': 'V_start (mV)', 'stop': 'V_stop (mV)', 'step': 'V_step (mV)',
+                                           'pulse': 'V_pulse (mV)', 'period': 'Pulse Period (ms)',
+                                           'width': 'Pulse Width (ms)'}
+        self.param_input_limits |= {'clean_s': (0, 'time_max'), 'dep_s': (0, 'time_max'),
+                                    'clean_mv': ('mv_min', 'mv_max'), 'dep_mv': ('mv_min', 'mv_max'),
+                                    'start': ('mv_min', 'mv_max'), 'stop': ('mv_min', 'mv_max'),
+                                    'step': (1, 1000), 'pulse': (1, 1000), 'period': (1, 1000),
+                                    'width': (1, 1000)}
+        self.param_converters |= {'clean_s': param_test_uint16, 'dep_s': param_test_uint16,
+                                  'clean_mv': abs_mv_to_dac, 'dep_mv': abs_mv_to_dac,
+                                  'start': abs_mv_to_dac, 'stop': abs_mv_to_dac, 'step': rel_mv_to_dac,
+                                  'pulse': rel_mv_to_dac, 'period': param_test_uint16,
+                                  'width': param_test_non_zero_uint16}
+        self.defaults |= {'clean_s': 0, 'dep_s': 0, 'clean_mv': 0, 'dep_mv': 0}
+
+        if params:
+            self.progress_max = abs(self.params['stop'] - self.params['start'])
+            self.progress_start = self.params['start']
+            self.progress_stop = self.params['stop']
+            self.progress_scan = 0
+            self.progress_iters = 1
+        else:
+            self.progress_max = 0
+        self.data_bytes = self.calculate_data_bytes()
 
     def get_progress(self):
+        if self.handler_instance.done:
+            return 100
         try:
-            return 1 - (abs(self.stop_mv - self.data['swv'][-1][0][-1])/self.max_mv)
+            return (1 - abs(
+                self.progress_stop - self.handler_instance.data['voltage'][-1]) / self.progress_max) * 100
         except IndexError:
-            return 0
\ No newline at end of file
+            return 0
diff --git a/dstat_interface/core/interface/__init__.py b/dstat_interface/core/interface/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/dstat_interface/core/interface/acv.glade b/dstat_interface/core/interface/acv.glade
deleted file mode 100644
index d664e83..0000000
--- a/dstat_interface/core/interface/acv.glade
+++ /dev/null
@@ -1,445 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkWindow" id="window1">
-    <property name="can_focus">False</property>
-    <child>
-      <object class="GtkScrolledWindow" id="scrolledwindow1">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <child>
-          <object class="GtkViewport" id="viewport1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="shadow_type">none</property>
-            <child>
-              <object class="GtkBox" id="vbox1">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkFrame" id="frame2">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">out</property>
-                    <child>
-                      <object class="GtkAlignment" id="alignment2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="bottom_padding">5</property>
-                        <property name="left_padding">5</property>
-                        <property name="right_padding">5</property>
-                        <child>
-                          <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row_homogeneous">True</property>
-                            <child>
-                              <object class="GtkLabel" id="label8">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="hexpand">True</property>
-                                <property name="label" translatable="yes">Start (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label9">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="hexpand">True</property>
-                                <property name="label" translatable="yes">Stop (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label10">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="hexpand">True</property>
-                                <property name="label" translatable="yes">Slope (mV/s)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="start_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="stop_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="slope_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label11">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="hexpand">True</property>
-                                <property name="label" translatable="yes">Amplitude (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">3</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label12">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="hexpand">True</property>
-                                <property name="label" translatable="yes">Frequency (Hz)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">4</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="amplitude_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">3</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="freq_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">4</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                    <child type="label">
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Experiment</property>
-                        <property name="use_markup">True</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="padding">2</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkFrame" id="frame1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">out</property>
-                    <child>
-                      <object class="GtkAlignment" id="alignment1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="bottom_padding">5</property>
-                        <property name="left_padding">5</property>
-                        <property name="right_padding">5</property>
-                        <child>
-                          <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row_homogeneous">True</property>
-                            <child>
-                              <object class="GtkLabel" id="label3">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Potential (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label4">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Time (s)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label6">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="hexpand">True</property>
-                                <property name="label" translatable="yes">Cleaning</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label7">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="hexpand">True</property>
-                                <property name="label" translatable="yes">Deposition</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="clean_mV">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="dep_mV">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="dep_s">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="clean_s">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label5">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">9</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                    <child type="label">
-                      <object class="GtkLabel" id="label1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Preconditioning</property>
-                        <property name="use_markup">True</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="padding">2</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label13">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">Experiment not yet implemented</property>
-                    <attributes>
-                      <attribute name="weight" value="bold"/>
-                    </attributes>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="padding">6</property>
-                    <property name="position">2</property>
-                  </packing>
-                </child>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>
diff --git a/dstat_interface/core/interface/adc_pot.glade b/dstat_interface/core/interface/adc_pot.glade
deleted file mode 100644
index fcb0d6e..0000000
--- a/dstat_interface/core/interface/adc_pot.glade
+++ /dev/null
@@ -1,412 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkListStore" id="gain_liststore">
-    <columns>
-      <!-- column-name index -->
-      <column type="guint"/>
-      <!-- column-name label -->
-      <column type="gchararray"/>
-      <!-- column-name command -->
-      <column type="gchararray"/>
-    </columns>
-    <data>
-      <row>
-        <col id="0">0</col>
-        <col id="1" translatable="yes">100 Ω (15 mA FS)</col>
-        <col id="2" translatable="yes">0</col>
-      </row>
-      <row>
-        <col id="0">1</col>
-        <col id="1" translatable="yes">300 Ω (5 mA FS)</col>
-        <col id="2" translatable="yes">1</col>
-      </row>
-      <row>
-        <col id="0">2</col>
-        <col id="1" translatable="yes">3 kΩ (500 µA FS)</col>
-        <col id="2" translatable="yes">2</col>
-      </row>
-      <row>
-        <col id="0">3</col>
-        <col id="1" translatable="yes">30 kΩ (50 µA FS)</col>
-        <col id="2" translatable="yes">3</col>
-      </row>
-      <row>
-        <col id="0">4</col>
-        <col id="1" translatable="yes">300 kΩ (5 µA FS)</col>
-        <col id="2" translatable="yes">4</col>
-      </row>
-      <row>
-        <col id="0">5</col>
-        <col id="1" translatable="yes">3 MΩ (500 nA FS)</col>
-        <col id="2" translatable="yes">5</col>
-      </row>
-      <row>
-        <col id="0">6</col>
-        <col id="1" translatable="yes">30 MΩ (50 nA FS)</col>
-        <col id="2" translatable="yes">6</col>
-      </row>
-      <row>
-        <col id="0">7</col>
-        <col id="1" translatable="yes">500 MΩ (3 nA FS)</col>
-        <col id="2" translatable="yes">7</col>
-      </row>
-    </data>
-  </object>
-  <object class="GtkListStore" id="pga_liststore">
-    <columns>
-      <!-- column-name index -->
-      <column type="guint"/>
-      <!-- column-name label -->
-      <column type="gchararray"/>
-      <!-- column-name command -->
-      <column type="gchararray"/>
-    </columns>
-    <data>
-      <row>
-        <col id="0">0</col>
-        <col id="1" translatable="yes">1x</col>
-        <col id="2" translatable="yes">0</col>
-      </row>
-      <row>
-        <col id="0">1</col>
-        <col id="1" translatable="yes">2x</col>
-        <col id="2" translatable="yes">1</col>
-      </row>
-      <row>
-        <col id="0">2</col>
-        <col id="1" translatable="yes">4x</col>
-        <col id="2" translatable="yes">2</col>
-      </row>
-      <row>
-        <col id="0">3</col>
-        <col id="1" translatable="yes">8x</col>
-        <col id="2" translatable="yes">3</col>
-      </row>
-      <row>
-        <col id="0">4</col>
-        <col id="1" translatable="yes">16x</col>
-        <col id="2" translatable="yes">4</col>
-      </row>
-      <row>
-        <col id="0">5</col>
-        <col id="1" translatable="yes">32x</col>
-        <col id="2" translatable="yes">5</col>
-      </row>
-      <row>
-        <col id="0">6</col>
-        <col id="1" translatable="yes">64x</col>
-        <col id="2" translatable="yes">6</col>
-      </row>
-    </data>
-  </object>
-  <object class="GtkListStore" id="srate_liststore">
-    <columns>
-      <!-- column-name index -->
-      <column type="guint"/>
-      <!-- column-name label -->
-      <column type="gchararray"/>
-      <!-- column-name command -->
-      <column type="gchararray"/>
-    </columns>
-    <data>
-      <row>
-        <col id="0">0</col>
-        <col id="1" translatable="yes">2.5 Hz</col>
-        <col id="2" translatable="yes">03</col>
-      </row>
-      <row>
-        <col id="0">1</col>
-        <col id="1" translatable="yes">5 Hz</col>
-        <col id="2" translatable="yes">13</col>
-      </row>
-      <row>
-        <col id="0">2</col>
-        <col id="1" translatable="yes">10 Hz</col>
-        <col id="2" translatable="yes">23</col>
-      </row>
-      <row>
-        <col id="0">3</col>
-        <col id="1" translatable="yes">15 Hz</col>
-        <col id="2" translatable="yes">33</col>
-      </row>
-      <row>
-        <col id="0">4</col>
-        <col id="1" translatable="yes">25 Hz</col>
-        <col id="2" translatable="yes">43</col>
-      </row>
-      <row>
-        <col id="0">5</col>
-        <col id="1" translatable="yes">30 Hz</col>
-        <col id="2" translatable="yes">53</col>
-      </row>
-      <row>
-        <col id="0">6</col>
-        <col id="1" translatable="yes">50 Hz</col>
-        <col id="2" translatable="yes">63</col>
-      </row>
-      <row>
-        <col id="0">7</col>
-        <col id="1" translatable="yes">60 Hz</col>
-        <col id="2" translatable="yes">72</col>
-      </row>
-      <row>
-        <col id="0">8</col>
-        <col id="1" translatable="yes">100 Hz</col>
-        <col id="2" translatable="yes">82</col>
-      </row>
-      <row>
-        <col id="0">9</col>
-        <col id="1" translatable="yes">500 Hz</col>
-        <col id="2" translatable="yes">92</col>
-      </row>
-      <row>
-        <col id="0">10</col>
-        <col id="1" translatable="yes">1 kHz</col>
-        <col id="2" translatable="yes">A1</col>
-      </row>
-      <row>
-        <col id="0">11</col>
-        <col id="1" translatable="yes">2 kHz</col>
-        <col id="2" translatable="yes">B0</col>
-      </row>
-      <row>
-        <col id="0">12</col>
-        <col id="1" translatable="yes">3.75 kHz</col>
-        <col id="2" translatable="yes">C0</col>
-      </row>
-      <row>
-        <col id="0">13</col>
-        <col id="1" translatable="yes">7.5 kHz</col>
-        <col id="2" translatable="yes">D0</col>
-      </row>
-      <row>
-        <col id="0">14</col>
-        <col id="1" translatable="yes">15 kHz</col>
-        <col id="2" translatable="yes">E0</col>
-      </row>
-      <row>
-        <col id="0">15</col>
-        <col id="1" translatable="yes">30 kHz</col>
-        <col id="2" translatable="yes">F0</col>
-      </row>
-    </data>
-  </object>
-  <object class="GtkWindow" id="window1">
-    <property name="can_focus">False</property>
-    <child>
-      <object class="GtkBox" id="vbox1">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <child>
-          <object class="GtkBox" id="vbox2">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="orientation">vertical</property>
-            <child>
-              <object class="GtkFrame" id="adc_frame">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label_xalign">0</property>
-                <property name="shadow_type">out</property>
-                <child>
-                  <object class="GtkAlignment" id="alignment1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="bottom_padding">5</property>
-                    <property name="left_padding">5</property>
-                    <property name="right_padding">5</property>
-                    <child>
-                      <object class="GtkGrid">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="row_homogeneous">True</property>
-                        <property name="column_homogeneous">True</property>
-                        <child>
-                          <object class="GtkLabel" id="label2">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="tooltip_text" translatable="yes">Gain of the ADC's programmable gain amplifier. Default is 2 - gives full scale input.</property>
-                            <property name="label" translatable="yes">PGA Setting</property>
-                          </object>
-                          <packing>
-                            <property name="left_attach">0</property>
-                            <property name="top_attach">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkLabel" id="label3">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="label" translatable="yes">Sample Rate</property>
-                          </object>
-                          <packing>
-                            <property name="left_attach">0</property>
-                            <property name="top_attach">1</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkCheckButton" id="buffer_checkbutton">
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
-                            <property name="receives_default">False</property>
-                            <property name="active">True</property>
-                            <property name="draw_indicator">True</property>
-                          </object>
-                          <packing>
-                            <property name="left_attach">1</property>
-                            <property name="top_attach">2</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkLabel" id="label4">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="tooltip_text" translatable="yes">The ADC's input buffer greatly increases input impedance. Should generally be enabled.</property>
-                            <property name="label" translatable="yes">Input Buffer</property>
-                          </object>
-                          <packing>
-                            <property name="left_attach">0</property>
-                            <property name="top_attach">2</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkComboBox" id="pga_combobox">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="model">pga_liststore</property>
-                          </object>
-                          <packing>
-                            <property name="left_attach">1</property>
-                            <property name="top_attach">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkComboBox" id="srate_combobox">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="model">srate_liststore</property>
-                          </object>
-                          <packing>
-                            <property name="left_attach">1</property>
-                            <property name="top_attach">1</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkLabel" id="label7">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="tooltip_text" translatable="yes">Short CE and RE terminals together.</property>
-                            <property name="label" translatable="yes">2 Electrode Mode</property>
-                          </object>
-                          <packing>
-                            <property name="left_attach">0</property>
-                            <property name="top_attach">3</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkCheckButton" id="short_checkbutton">
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
-                            <property name="receives_default">False</property>
-                            <property name="draw_indicator">True</property>
-                          </object>
-                          <packing>
-                            <property name="left_attach">1</property>
-                            <property name="top_attach">3</property>
-                          </packing>
-                        </child>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-                <child type="label">
-                  <object class="GtkLabel" id="label1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">ADC Settings</property>
-                    <property name="use_markup">True</property>
-                  </object>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkFrame" id="pstat_frame">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label_xalign">0</property>
-                <property name="shadow_type">out</property>
-                <child>
-                  <object class="GtkAlignment" id="alignment2">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="bottom_padding">5</property>
-                    <property name="left_padding">5</property>
-                    <property name="right_padding">5</property>
-                    <child>
-                      <object class="GtkGrid">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="column_homogeneous">True</property>
-                        <child>
-                          <object class="GtkLabel" id="label6">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="tooltip_text" translatable="yes">Gain of the I-V converter. Increasing this improves S/N, unlike the ADC's PGA.</property>
-                            <property name="label" translatable="yes">Gain</property>
-                          </object>
-                          <packing>
-                            <property name="left_attach">0</property>
-                            <property name="top_attach">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkComboBox" id="gain_combobox">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="model">gain_liststore</property>
-                          </object>
-                          <packing>
-                            <property name="left_attach">1</property>
-                            <property name="top_attach">0</property>
-                          </packing>
-                        </child>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-                <child type="label">
-                  <object class="GtkLabel" id="label5">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">Potentiostat Settings</property>
-                    <property name="use_markup">True</property>
-                  </object>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="padding">2</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="padding">2</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>
diff --git a/dstat_interface/core/interface/adc_pot.py b/dstat_interface/core/interface/adc_pot.py
deleted file mode 100755
index 77efe5e..0000000
--- a/dstat_interface/core/interface/adc_pot.py
+++ /dev/null
@@ -1,139 +0,0 @@
-#!/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 os.path
-from pkg_resources import parse_version
-
-try:
-    import gi
-    gi.require_version('Gtk', '3.0')
-    from gi.repository import Gtk
-except ImportError:
-    print "ERR: GTK not available"
-    sys.exit(1)
-
-from ..errors import InputError, VarError
-from ..dstat import state
-
-mod_dir = os.path.dirname(os.path.abspath(__file__))
-
-class adc_pot(object):
-    def __init__(self):
-        self.builder = Gtk.Builder()
-        self.builder.add_from_file(os.path.join(mod_dir,'adc_pot.glade'))
-        self.builder.connect_signals(self)
-        self.cell = Gtk.CellRendererText()
-        
-        ui_keys = ['buffer_true',
-                   'short_true',
-                   'pga_index',
-                   'srate_index',
-                   'gain_index'
-                   ]
-        ui_cont = map(self.builder.get_object, ['buffer_checkbutton',
-                                                'short_checkbutton',
-                                                'pga_combobox',
-                                                'srate_combobox',
-                                                'gain_combobox'
-                                                ]
-                      )
-        self.ui = dict(zip(ui_keys, ui_cont))
-        
-        #initialize comboboxes
-        self.ui['pga_index'].pack_start(self.cell, True)
-        self.ui['pga_index'].add_attribute(self.cell, 'text', 1)
-        self.ui['pga_index'].set_active(1)
-
-        self.ui['srate_index'].pack_start(self.cell, True)
-        self.ui['srate_index'].add_attribute(self.cell, 'text', 1)
-        self.ui['srate_index'].set_active(7)
-        
-        self.gain_liststore = self.builder.get_object('gain_liststore')
-        self.ui['gain_index'].pack_start(self.cell, True)
-        self.ui['gain_index'].add_attribute(self.cell, 'text', 1)
-        # self.ui['gain_index'].set_active(2)
-        
-        self._params = {}
-    
-    @property
-    def params(self):
-        """Dict of parameters."""
-        try:
-            self._get_params()
-        except InputError as e:
-            raise e
-        finally:
-            return self._params
-    
-    def _get_params(self):
-        """Updates self._params from UI."""
-        for i in self.ui:
-            self._params[i] = self.ui[i].get_active()
-        
-        srate_model = self.ui['srate_index'].get_model()
-        self._params['adc_rate'] = srate_model[self._params['srate_index']][2]
-        srate = srate_model[self._params['srate_index']][1]
-        
-        if srate.endswith("kHz"):
-            sample_rate = float(srate.rstrip(" kHz"))*1000
-        else:
-            sample_rate = float(srate.rstrip(" Hz"))
-        
-        self._params['adc_rate_hz'] = sample_rate
-        
-        pga_model = self.ui['pga_index'].get_model()
-        self._params['adc_pga'] = pga_model[self._params['pga_index']][2]
-        
-        gain_model = self.ui['gain_index'].get_model()
-        self._params['gain'] = gain_model[self._params['gain_index']][2]
-        if self._params['gain_index'] not in range(len(gain_model)):
-            raise InputError(self._params['gain_index'],
-                             "Select a potentiostat gain.")
-        
-    @params.setter
-    def params(self, params):
-        if self._params is {}:
-            self._params = dict.fromkeys(self.ui.keys())
-        
-        for i in self._params:
-            if i in params:
-                self._params[i] = params[i]
-        self._set_params()
-
-    def _set_params(self):
-        """Updates UI with new parameters."""
-        for i in self.ui:
-            self.ui[i].set_active(self._params[i])
-            
-    def set_version(self, boost=None):
-        """ Sets menus for DStat version. """
-        try:
-            if self.version == state.board_instance:
-                return
-        except AttributeError:
-            pass
-            
-        self.version = state.board_instance
-        
-        self.gain_liststore.clear()
-        
-        for n, i in enumerate(self.version.gain_labels):
-            self.gain_liststore.append((n, i, str(n)))
-            
-        self.ui['gain_index'].set_active(self.version.gain_default_index)
\ No newline at end of file
diff --git a/dstat_interface/core/interface/analysis_options.glade b/dstat_interface/core/interface/analysis_options.glade
deleted file mode 100644
index dda2e6c..0000000
--- a/dstat_interface/core/interface/analysis_options.glade
+++ /dev/null
@@ -1,159 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkDialog" id="analysis_dialog">
-    <property name="can_focus">False</property>
-    <property name="border_width">5</property>
-    <property name="title" translatable="yes">Analysis Options…</property>
-    <property name="type_hint">dialog</property>
-    <child internal-child="vbox">
-      <object class="GtkBox" id="dialog-vbox1">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox" id="dialog-action_area1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="layout_style">end</property>
-            <child>
-              <placeholder/>
-            </child>
-            <child>
-              <object class="GtkButton" id="ok_button">
-                <property name="label">gtk-ok</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="use_stock">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkGrid">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <child>
-              <object class="GtkSpinButton" id="stats_start_spin">
-                <property name="visible">True</property>
-                <property name="sensitive">False</property>
-                <property name="can_focus">True</property>
-                <property name="invisible_char">●</property>
-                <property name="text" translatable="yes">0.00</property>
-                <property name="primary_icon_activatable">False</property>
-                <property name="secondary_icon_activatable">False</property>
-                <property name="adjustment">stats_start_adj</property>
-                <property name="climb_rate">0.050000000000000003</property>
-                <property name="digits">2</property>
-                <property name="numeric">True</property>
-              </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkSpinButton" id="stats_stop_spin">
-                <property name="visible">True</property>
-                <property name="sensitive">False</property>
-                <property name="can_focus">True</property>
-                <property name="invisible_char">●</property>
-                <property name="text" translatable="yes">0.00</property>
-                <property name="primary_icon_activatable">False</property>
-                <property name="secondary_icon_activatable">False</property>
-                <property name="adjustment">stats_stop_adj</property>
-                <property name="climb_rate">0.050000000000000003</property>
-                <property name="digits">2</property>
-                <property name="numeric">True</property>
-                <property name="update_policy">if-valid</property>
-              </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">2</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkCheckButton" id="stats_button">
-                <property name="label" translatable="yes">Generate Summary Stats</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
-                <property name="halign">center</property>
-                <property name="image_position">bottom</property>
-                <property name="draw_indicator">True</property>
-              </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">0</property>
-                <property name="width">2</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkCheckButton" id="stats_start_button">
-                <property name="label" translatable="yes">Stat Start (mV or s)</property>
-                <property name="visible">True</property>
-                <property name="sensitive">False</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
-                <property name="hexpand">True</property>
-                <property name="draw_indicator">True</property>
-              </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkCheckButton" id="stats_stop_button">
-                <property name="label" translatable="yes">Stat Stop (mV or s)</property>
-                <property name="visible">True</property>
-                <property name="sensitive">False</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
-                <property name="hexpand">True</property>
-                <property name="draw_indicator">True</property>
-              </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">2</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-    <action-widgets>
-      <action-widget response="0">ok_button</action-widget>
-    </action-widgets>
-  </object>
-  <object class="GtkAdjustment" id="stats_start_adj">
-    <property name="lower">-3000</property>
-    <property name="upper">9999</property>
-    <property name="step_increment">0.5</property>
-    <property name="page_increment">10</property>
-  </object>
-  <object class="GtkAdjustment" id="stats_stop_adj">
-    <property name="lower">-3000</property>
-    <property name="upper">9999</property>
-    <property name="step_increment">0.5</property>
-    <property name="page_increment">10</property>
-  </object>
-</interface>
diff --git a/dstat_interface/core/interface/calib.glade b/dstat_interface/core/interface/calib.glade
deleted file mode 100644
index ff4b40f..0000000
--- a/dstat_interface/core/interface/calib.glade
+++ /dev/null
@@ -1,363 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkWindow" id="window1">
-    <property name="can_focus">False</property>
-    <child>
-      <object class="GtkScrolledWindow" id="scrolledwindow1">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <child>
-          <object class="GtkViewport" id="viewport1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="shadow_type">none</property>
-            <child>
-              <object class="GtkBox" id="vbox1">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="row_homogeneous">True</property>
-                    <child>
-                      <object class="GtkEntry" id="time_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="max_length">5</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Measurement Time (s)</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Resistor</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label3">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Trim Value (ADC units)</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label4">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">100 Ω</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">3</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label5">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">3 kΩ</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">4</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label6">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">30 kΩ</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">5</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label7">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">300 kΩ</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">6</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label8">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">3 MΩ</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">7</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label9">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">30 MΩ</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">8</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label10">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">100 MΩ</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">9</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="30k_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="max_length">6</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">5</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="3k_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="max_length">6</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">4</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="100_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="max_length">6</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">3</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="300k_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="max_length">6</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">6</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="3M_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="max_length">6</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">7</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="30M_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="max_length">6</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">8</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="100M_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="max_length">6</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">9</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkButton" id="read_button">
-                        <property name="label" translatable="yes">Read from EEPROM</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">True</property>
-                        <signal name="clicked" handler="on_read_button_clicked" swapped="no"/>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">11</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkButton" id="write_button">
-                        <property name="label" translatable="yes">Write to EEPROM</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">True</property>
-                        <signal name="clicked" handler="on_write_button_clicked" swapped="no"/>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">12</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkButton" id="measure_button">
-                        <property name="label" translatable="yes">Measure</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">True</property>
-                        <signal name="clicked" handler="on_measure_button_clicked" swapped="no"/>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">10</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkSeparator">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">1</property>
-                        <property name="width">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <placeholder/>
-                    </child>
-                    <child>
-                      <placeholder/>
-                    </child>
-                    <child>
-                      <placeholder/>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label13">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="hexpand">True</property>
-                    <property name="vexpand">True</property>
-                    <property name="label" translatable="yes">Measure with WE open.
-Offsets cannot exceed 16 bit unsigned int.</property>
-                    <attributes>
-                      <attribute name="weight" value="bold"/>
-                    </attributes>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>
diff --git a/dstat_interface/core/interface/chronoamp.glade b/dstat_interface/core/interface/chronoamp.glade
deleted file mode 100644
index e0a22de..0000000
--- a/dstat_interface/core/interface/chronoamp.glade
+++ /dev/null
@@ -1,186 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkListStore" id="ca_list">
-    <columns>
-      <!-- column-name millivolts -->
-      <column type="gint"/>
-      <!-- column-name seconds -->
-      <column type="guint"/>
-    </columns>
-  </object>
-  <object class="GtkWindow" id="window1">
-    <property name="can_focus">False</property>
-    <property name="default_width">300</property>
-    <property name="default_height">500</property>
-    <child>
-      <object class="GtkScrolledWindow" id="scrolledwindow1">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <child>
-          <object class="GtkViewport" id="viewport1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="shadow_type">none</property>
-            <child>
-              <object class="GtkBox" id="vbox1">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="row_homogeneous">True</property>
-                    <property name="column_homogeneous">True</property>
-                    <child>
-                      <object class="GtkLabel" id="label1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Potential (mV)</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="potential_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">5</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="truncate_multiline">True</property>
-                        <property name="caps_lock_warning">False</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Time (s)</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="time_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">5</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="truncate_multiline">True</property>
-                        <property name="caps_lock_warning">False</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkButton" id="add_button">
-                        <property name="label">gtk-add</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">True</property>
-                        <property name="use_stock">True</property>
-                        <signal name="clicked" handler="on_add_button_clicked" swapped="no"/>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkButton" id="remove_button">
-                        <property name="label">gtk-remove</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">True</property>
-                        <property name="use_stock">True</property>
-                        <signal name="clicked" handler="on_remove_button_clicked" swapped="no"/>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">2</property>
-                      </packing>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkTreeView" id="treeview">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="model">ca_list</property>
-                        <property name="reorderable">True</property>
-                        <property name="rules_hint">True</property>
-                        <property name="enable_search">False</property>
-                        <property name="search_column">0</property>
-                        <property name="fixed_height_mode">True</property>
-                        <property name="show_expanders">False</property>
-                        <property name="rubber_banding">True</property>
-                        <property name="enable_grid_lines">both</property>
-                        <child internal-child="selection">
-                          <object class="GtkTreeSelection"/>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkStatusbar" id="statusbar">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="spacing">2</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>
diff --git a/dstat_interface/core/interface/cv.glade b/dstat_interface/core/interface/cv.glade
deleted file mode 100644
index 9cc5d7f..0000000
--- a/dstat_interface/core/interface/cv.glade
+++ /dev/null
@@ -1,362 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkWindow" id="window1">
-    <property name="can_focus">False</property>
-    <child>
-      <object class="GtkScrolledWindow" id="scrolledwindow1">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <child>
-          <object class="GtkViewport" id="viewport1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="shadow_type">none</property>
-            <child>
-              <object class="GtkBox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkFrame" id="frame1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">out</property>
-                    <child>
-                      <object class="GtkAlignment" id="alignment1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="bottom_padding">5</property>
-                        <property name="left_padding">5</property>
-                        <property name="right_padding">5</property>
-                        <child>
-                          <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row_homogeneous">True</property>
-                            <property name="column_homogeneous">True</property>
-                            <child>
-                              <object class="GtkLabel" id="label3">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="vexpand">False</property>
-                                <property name="label" translatable="yes">Potential (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label4">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Time (s)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label5">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label6">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Cleaning</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label7">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Deposition</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="clean_mV">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="dep_mV">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="dep_s">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="clean_s">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                    <child type="label">
-                      <object class="GtkLabel" id="label1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Preconditioning</property>
-                        <property name="use_markup">True</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="padding">2</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkFrame" id="frame2">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">out</property>
-                    <child>
-                      <object class="GtkAlignment" id="alignment2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="bottom_padding">5</property>
-                        <property name="left_padding">5</property>
-                        <property name="right_padding">5</property>
-                        <child>
-                          <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row_homogeneous">True</property>
-                            <property name="column_homogeneous">True</property>
-                            <child>
-                              <object class="GtkLabel" id="label8">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Start (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label9">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Vertex 1 (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label10">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Vertex 2 (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="start_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="v1_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="v2_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label11">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Slope (mV/s)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">3</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label12">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Scans</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">4</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="slope_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">3</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="scans_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">4</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                    <child type="label">
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Experiment</property>
-                        <property name="use_markup">True</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="padding">1</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>
diff --git a/dstat_interface/core/interface/data_view.py b/dstat_interface/core/interface/data_view.py
deleted file mode 100644
index e7c4db6..0000000
--- a/dstat_interface/core/interface/data_view.py
+++ /dev/null
@@ -1,81 +0,0 @@
-from __future__ import division, absolute_import, print_function, unicode_literals
-
-import logging
-logger = logging.getLogger(__name__)
-
-from collections import OrderedDict
-
-try:
-    import gi
-    gi.require_version('Gtk', '3.0')
-    from gi.repository import Gtk
-except ImportError:
-    print("ERR: GTK not available")
-    sys.exit(1)
-    
-class DataPage(object):
-    def __init__(self, notebook, name="Data"):
-        """Make new notebook page and adds to notebook."""
-        self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
-        self.combobox = Gtk.ComboBoxText()
-        self.scroll = Gtk.ScrolledWindow(vexpand=True)
-        self.textview = Gtk.TextView(cursor_visible=False, monospace=True,
-                                     editable=False)
-        self.scroll.add(self.textview)                             
-        self.box.add(self.combobox)
-        self.box.add(self.scroll)
-        self.name = name
-        self.buffers = {}
-        
-        self.combobox.connect('changed', self.combobox_changed)
-        
-        notebook.append_page(self.box, Gtk.Label(label=name))
-    
-    def add_exp(self, exp):
-        """Add all data from exp to page."""
-        for name, df in exp.df.items():
-            self.combobox.append(id=name, text=name)
-            self.buffers[name] = Gtk.TextBuffer()
-            self.buffers[name].set_text(df.to_string())
-            self.box.show_all()
-        
-        self.combobox.set_active(0)
-    
-    def clear_exps(self):
-        self.combobox.remove_all()
-        self.buffers = {}
-        
-    def combobox_changed(self, object):
-        """Switch displayed data buffer."""
-        try:
-            self.textview.set_buffer(
-                self.buffers[self.combobox.get_active_id()]
-            )
-        except KeyError:
-            pass
-        
-class InfoPage(object):
-    def __init__(self, notebook, name="Info"):
-        """Make new notebook page and adds to notebook."""
-        self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
-        self.buffer = Gtk.TextBuffer()
-        self.scroll = Gtk.ScrolledWindow(vexpand=True)
-        self.textview = Gtk.TextView(cursor_visible=False, monospace=True,
-                                     editable=False, buffer=self.buffer)
-        self.scroll.add(self.textview)
-        self.box.add(self.scroll)
-        
-        self.name = name
-        
-        notebook.append_page(self.box, Gtk.Label(label=name))
-        self.box.show_all()
-        
-    def clear(self):
-        """Clear buffer"""
-        self.buffer.set_text('')
-    
-    def set_text(self, text):
-        self.buffer.set_text(text)
-    
-    def add_line(self, line):
-        self.buffer.insert_at_cursor('{}\n'.format(line))
\ No newline at end of file
diff --git a/dstat_interface/core/interface/db.glade b/dstat_interface/core/interface/db.glade
deleted file mode 100644
index e65d520..0000000
--- a/dstat_interface/core/interface/db.glade
+++ /dev/null
@@ -1,204 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkWindow" id="db_window">
-    <property name="can_focus">False</property>
-    <property name="title" translatable="yes">Database</property>
-    <property name="window_position">center-on-parent</property>
-    <property name="destroy_with_parent">True</property>
-    <child>
-      <object class="GtkBox" id="vbox1">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <child>
-          <object class="GtkCheckButton" id="db_enable_checkbutton">
-            <property name="label" translatable="yes">Enable Database saving</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">False</property>
-            <property name="active">True</property>
-            <property name="draw_indicator">True</property>
-            <signal name="toggled" handler="on_db_enable_checkbutton_toggled" swapped="no"/>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkGrid" id="db_control_table">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="row_homogeneous">True</property>
-            <property name="column_homogeneous">True</property>
-            <child>
-              <object class="GtkLabel" id="label3">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">Measurement ID</property>
-              </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="label1">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">Experiment ID</property>
-              </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkEntry" id="exp_id_entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="invisible_char">●</property>
-                <property name="primary_icon_activatable">False</property>
-                <property name="secondary_icon_activatable">False</property>
-              </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkEntry" id="measure_id_entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="editable">False</property>
-                <property name="invisible_char">●</property>
-                <property name="primary_icon_activatable">False</property>
-                <property name="secondary_icon_activatable">False</property>
-              </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkEntry" id="patient_id_entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="editable">False</property>
-                <property name="invisible_char">●</property>
-                <property name="primary_icon_activatable">False</property>
-                <property name="secondary_icon_activatable">False</property>
-              </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">3</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="label2">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">Patient ID</property>
-              </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">3</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="exp_id_autogen_button">
-                <property name="label" translatable="yes">Generate New</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <signal name="clicked" handler="on_exp_id_autogen_button_clicked" swapped="no"/>
-              </object>
-              <packing>
-                <property name="left_attach">2</property>
-                <property name="top_attach">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="label4">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">DB Path</property>
-              </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">4</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkEntry" id="db_path_entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="invisible_char">●</property>
-                <property name="primary_icon_activatable">False</property>
-                <property name="secondary_icon_activatable">False</property>
-              </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">4</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="db_apply_button">
-                <property name="label" translatable="yes">Apply</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <signal name="clicked" handler="on_db_apply_button_clicked" swapped="no"/>
-              </object>
-              <packing>
-                <property name="left_attach">2</property>
-                <property name="top_attach">4</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="label5">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">Measurement Name</property>
-              </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">2</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkEntry" id="measure_name_entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="invisible_char">●</property>
-                <property name="primary_icon_activatable">False</property>
-                <property name="secondary_icon_activatable">False</property>
-              </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">2</property>
-              </packing>
-            </child>
-            <child>
-              <placeholder/>
-            </child>
-            <child>
-              <placeholder/>
-            </child>
-            <child>
-              <placeholder/>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>
diff --git a/dstat_interface/core/interface/db.py b/dstat_interface/core/interface/db.py
deleted file mode 100755
index e362292..0000000
--- a/dstat_interface/core/interface/db.py
+++ /dev/null
@@ -1,143 +0,0 @@
-#!/usr/bin/env python
-#     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 os
-import sys
-import logging
-from uuid import uuid4
-
-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)
-
-logger = logging.getLogger(__name__)
-
-class DB_Window(GObject.GObject):
-    __gsignals__ = {
-                    'db-reset' : (GObject.SIGNAL_RUN_LAST,
-                                        GObject.TYPE_NONE,
-                                        (GObject.TYPE_STRING,)
-                                        )
-                    }
-    def __init__(self):
-        GObject.GObject.__init__(self)
-        
-        self.builder = Gtk.Builder()
-        self.builder.add_from_file('interface/db.glade')
-        self.builder.connect_signals(self)
-        
-        self.window = self.builder.get_object('db_window')
-        # Make sure window isn't destroyed if checkbox hit.
-        self.window.connect('delete-event', self.on_delete_event)
-        self.db_control_table = self.builder.get_object('db_control_table')
-        
-        ui_keys = ['exp_id_entry',
-                   'measure_id_entry',
-                   'patient_id_entry',
-                   'measure_name_entry',
-                   'db_path_entry',
-                   'db_apply_button',
-                   'exp_id_autogen_button',
-                   'db_enable_checkbutton'
-                   ]
-        self.persistent_keys = ['db_path_entry','db_enable_checkbutton']
-        self.ui = {}
-        for i in ui_keys:
-            self.ui[i] = self.builder.get_object(i)
-        
-        self.metadata_map = [('experiment_uuid', 'exp_id_entry'),
-                             ('patient_id', 'patient_id_entry'),
-                             ('name', 'measure_name_entry')]
-        
-        self._params = {}
-    
-    @property
-    def persistent_params(self):
-        """Dict of parameters that should be saved."""
-        self._get_params()
-        return {k:self._params[k] for k in self.persistent_keys}
-        
-    @property
-    def params(self):
-        """Dict of parameters."""
-        self._get_params()      
-        return self._params
-    
-    def _get_params(self):
-        """Updates self._params from UI."""
-        for i in self.ui:
-            if i.endswith('checkbutton'):
-                self._params[i] = self.ui[i].get_active()
-            elif i.endswith('entry'):
-                text = self.ui[i].get_text()
-                if text == "":
-                    text = None
-                self._params[i] = text
-    
-    @params.setter
-    def params(self, params):
-        if self._params is {}:
-            self._params = dict.fromkeys(self.ui.keys())
-    
-        for i in self._params:
-            if i in params:
-                self._params[i] = params[i]
-        self._set_params()
-
-    def _set_params(self):
-        """Updates UI with new parameters."""
-        for i in self.ui:
-            if i.endswith('checkbutton'):
-                self.ui[i].set_active(self._params[i])
-            elif i.endswith('entry'):
-                if self._params[i] is not None:
-                    self.ui[i].set_text(self._params[i])
-                else:
-                    self.ui[i].set_text("")
-    
-    def update_from_metadata(self, metadata):
-        params = {k : metadata[j] for j, k in self.metadata_map
-                    if j in metadata}
-        self.params = params
-    
-    def show(self):
-        self.window.show_all()
-        self.on_db_enable_checkbutton_toggled()
-    
-    def on_db_enable_checkbutton_toggled(self, *args):
-        if self.ui['db_enable_checkbutton'].get_active():
-            self.db_control_table.show()
-        else:
-            self.db_control_table.hide()
-            
-    def on_exp_id_autogen_button_clicked(self, *args):
-        self.ui['exp_id_entry'].set_text(uuid4().hex)
-        
-    def on_db_apply_button_clicked(self, widget=None, *data):
-        self.emit('db-reset', self.params['db_path_entry'])
-        
-    def on_delete_event(self, widget=None, *data):
-        widget.hide()
-        return True
-        
-GObject.type_register(DB_Window)
\ No newline at end of file
diff --git a/dstat_interface/core/interface/dpv.glade b/dstat_interface/core/interface/dpv.glade
deleted file mode 100644
index 9143f88..0000000
--- a/dstat_interface/core/interface/dpv.glade
+++ /dev/null
@@ -1,404 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkWindow" id="window1">
-    <property name="can_focus">False</property>
-    <child>
-      <object class="GtkScrolledWindow" id="scrolledwindow1">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <child>
-          <object class="GtkViewport" id="viewport1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="shadow_type">none</property>
-            <child>
-              <object class="GtkBox" id="vbox1">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkFrame" id="frame1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">out</property>
-                    <child>
-                      <object class="GtkAlignment" id="alignment1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="bottom_padding">5</property>
-                        <property name="left_padding">5</property>
-                        <property name="right_padding">5</property>
-                        <child>
-                          <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row_homogeneous">True</property>
-                            <property name="column_homogeneous">True</property>
-                            <child>
-                              <object class="GtkEntry" id="clean_s">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="dep_s">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="dep_mV">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="clean_mV">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label7">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Deposition</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label6">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Cleaning</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label5">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label4">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Time (s)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label3">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Potential (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                    <child type="label">
-                      <object class="GtkLabel" id="label1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Preconditioning</property>
-                        <property name="use_markup">True</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="padding">2</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkFrame" id="frame2">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">out</property>
-                    <child>
-                      <object class="GtkAlignment" id="alignment2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="bottom_padding">5</property>
-                        <property name="left_padding">5</property>
-                        <property name="right_padding">5</property>
-                        <child>
-                          <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row_homogeneous">True</property>
-                            <property name="column_homogeneous">True</property>
-                            <child>
-                              <object class="GtkLabel" id="label8">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Start (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label9">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Stop (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label10">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Step Size (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="start_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="stop_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="step_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label11">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Pulse Height (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">3</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label12">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Pulse Width (ms)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">5</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="pulse_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">3</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="width_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">5</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label14">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="tooltip_text" translatable="yes">Scan both forwards and backwards.</property>
-                                <property name="label" translatable="yes">Pulse Period (ms)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">4</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="period_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">4</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                    <child type="label">
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Experiment</property>
-                        <property name="use_markup">True</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="padding">2</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label13">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">Note: ADC samples 1/(ADC Frequency) before end of pulse. 1/(ADC frequency) should be significantly shorter than pulse period to reduce capacitive current.</property>
-                    <property name="wrap">True</property>
-                    <property name="wrap_mode">word-char</property>
-                    <property name="width_chars">43</property>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">2</property>
-                  </packing>
-                </child>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>
diff --git a/dstat_interface/core/interface/dstatinterface.glade b/dstat_interface/core/interface/dstatinterface.glade
deleted file mode 100644
index 889e1d6..0000000
--- a/dstat_interface/core/interface/dstatinterface.glade
+++ /dev/null
@@ -1,737 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkAboutDialog" id="aboutdialog1">
-    <property name="can_focus">False</property>
-    <property name="border_width">5</property>
-    <property name="destroy_with_parent">True</property>
-    <property name="type_hint">dialog</property>
-    <property name="program_name">DStat-interface</property>
-    <property name="copyright" translatable="yes">© Michael Dryden 2014</property>
-    <property name="comments" translatable="yes">This software is licensed under the GNU GPL v3</property>
-    <property name="website">http://microfluidics.utoronto.ca/dstat</property>
-    <property name="website_label" translatable="yes">Wheeler Microfuidics Lab</property>
-    <property name="authors">Michael Dryden
-Thanks to Christian Fobel for help with Dropbot Plugin</property>
-    <property name="documenters"/>
-    <property name="logo_icon_name">image-missing</property>
-    <property name="license_type">gpl-3-0</property>
-    <child internal-child="vbox">
-      <object class="GtkBox" id="dialog-vbox1">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox" id="dialog-action_area1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="layout_style">end</property>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <placeholder/>
-        </child>
-      </object>
-    </child>
-  </object>
-  <object class="GtkTextBuffer" id="databuffer1"/>
-  <object class="GtkTextBuffer" id="databuffer2"/>
-  <object class="GtkImage" id="image1">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="halign">center</property>
-    <property name="stock">gtk-missing-image</property>
-  </object>
-  <object class="GtkImage" id="image2">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">image7</property>
-  </object>
-  <object class="GtkImage" id="image3">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="stock">gtk-save-as</property>
-  </object>
-  <object class="GtkImage" id="image4">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="stock">gtk-save-as</property>
-  </object>
-  <object class="GtkImage" id="image5">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="stock">gtk-open</property>
-  </object>
-  <object class="GtkImage" id="image6">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="stock">gtk-preferences</property>
-  </object>
-  <object class="GtkImage" id="image7">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">image7</property>
-  </object>
-  <object class="GtkImage" id="image8">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="stock">gtk-save-as</property>
-  </object>
-  <object class="GtkImage" id="image9">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">image7</property>
-  </object>
-  <object class="GtkListStore" id="serial_liststore">
-    <columns>
-      <!-- column-name serial -->
-      <column type="gchararray"/>
-    </columns>
-  </object>
-  <object class="GtkWindow" id="window1">
-    <property name="can_focus">False</property>
-    <property name="border_width">6</property>
-    <property name="title" translatable="yes">Main</property>
-    <property name="default_width">1200</property>
-    <property name="default_height">800</property>
-    <signal name="destroy" handler="on_window1_destroy" swapped="no"/>
-    <child>
-      <object class="GtkBox" id="vbox1">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <child>
-          <object class="GtkMenuBar" id="menubar1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <child>
-              <object class="GtkMenuItem" id="menuitem1">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">File</property>
-                <child type="submenu">
-                  <object class="GtkMenu" id="menu1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <child>
-                      <object class="GtkImageMenuItem" id="file_save_exp">
-                        <property name="label" translatable="yes">Save Experiment Data…</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="image">image8</property>
-                        <property name="use_stock">False</property>
-                        <signal name="activate" handler="on_file_save_exp_activate" swapped="no"/>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImageMenuItem" id="file_save_plot">
-                        <property name="label" translatable="yes">Save Plot…</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="image">image3</property>
-                        <property name="use_stock">False</property>
-                        <signal name="activate" handler="on_file_save_plot_activate" swapped="no"/>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImageMenuItem" id="file_save_params">
-                        <property name="label" translatable="yes">Save Parameters…</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="image">image4</property>
-                        <property name="use_stock">False</property>
-                        <signal name="activate" handler="on_file_save_params_activate" swapped="no"/>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImageMenuItem" id="file_load_params">
-                        <property name="label" translatable="yes">Load Parameters…</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="image">image5</property>
-                        <property name="use_stock">False</property>
-                        <signal name="activate" handler="on_file_load_params_activate" swapped="no"/>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImageMenuItem" id="gtk_quit">
-                        <property name="label">gtk-quit</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_gtk_quit_activate" swapped="no"/>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-              </object>
-            </child>
-            <child>
-              <object class="GtkMenuItem" id="menu_dstat">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">DStat</property>
-                <child type="submenu">
-                  <object class="GtkMenu" id="menu5">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <child>
-                      <object class="GtkImageMenuItem" id="menu_dstat_info">
-                        <property name="label">System Information</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="image">image7</property>
-                        <property name="use_stock">False</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImageMenuItem" id="menu_dstat_fw">
-                        <property name="label" translatable="yes">Firmware…</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="image">image2</property>
-                        <property name="use_stock">False</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImageMenuItem" id="menu_dstat_reset">
-                        <property name="label">Reset EEPROM</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="image">image9</property>
-                        <property name="use_stock">False</property>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-              </object>
-            </child>
-            <child>
-              <object class="GtkMenuItem" id="menu_dropbot">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">Dropbot</property>
-                <child type="submenu">
-                  <object class="GtkMenu" id="menu2">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <child>
-                      <object class="GtkImageMenuItem" id="menu_dropbot_connect">
-                        <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>
-                    </child>
-                    <child>
-                      <object class="GtkImageMenuItem" id="menu_dropbot_disconnect">
-                        <property name="label">gtk-disconnect</property>
-                        <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>
-                    </child>
-                  </object>
-                </child>
-              </object>
-            </child>
-            <child>
-              <object class="GtkMenuItem" id="menu_analysis">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">Analysis</property>
-                <child type="submenu">
-                  <object class="GtkMenu" id="menu4">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <child>
-                      <object class="GtkImageMenuItem" id="menu_analysis_options">
-                        <property name="label" translatable="yes">Analysis Options…</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="image">image6</property>
-                        <property name="use_stock">False</property>
-                        <signal name="activate" handler="on_menu_analysis_options_activate" swapped="no"/>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-              </object>
-            </child>
-            <child>
-              <object class="GtkMenuItem" id="menuitem4">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">About</property>
-                <child type="submenu">
-                  <object class="GtkMenu" id="menu3">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <child>
-                      <object class="GtkImageMenuItem" id="gtk_about">
-                        <property name="label">gtk-about</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_gtk_about_activate" swapped="no"/>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-              </object>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkPaned">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="position">300</property>
-            <property name="position_set">True</property>
-            <property name="wide_handle">True</property>
-            <child>
-              <object class="GtkBox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkBox" id="gain_adc_box">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="orientation">vertical</property>
-                        <child>
-                          <placeholder/>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label7">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Experiment:</property>
-                        <attributes>
-                          <attribute name="weight" value="semibold"/>
-                        </attributes>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                        <property name="padding">10</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <placeholder/>
-                    </child>
-                    <child>
-                      <object class="GtkComboBoxText" id="expcombobox">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">3</property>
-                      </packing>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkBox" id="exp_section_box">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <placeholder/>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <child>
-                      <object class="GtkButton" id="pot_start">
-                        <property name="label">gtk-execute</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">True</property>
-                        <property name="use_stock">True</property>
-                        <signal name="clicked" handler="on_pot_start_clicked" swapped="no"/>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkButton" id="pot_stop">
-                        <property name="label">gtk-stop</property>
-                        <property name="visible">True</property>
-                        <property name="sensitive">False</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">True</property>
-                        <property name="use_stock">True</property>
-                        <signal name="clicked" handler="on_pot_stop_clicked" swapped="no"/>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkSpinner" id="spinner">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">2</property>
-                      </packing>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkProgressBar" id="exp_progressbar">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="show_text">True</property>
-                    <property name="margin_top">10</property>
-                    <property name="margin_bottom">15</property>
-                    <property name="margin_right">5</property>
-                    <property name="margin_left">2</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">3</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
-                <property name="resize">False</property>
-                <property name="shrink">True</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkNotebook" id="main_notebook">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="scrollable">True</property>
-                <child>
-                  <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkNotebook" id="plot_notebook">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="tab_pos">bottom</property>
-                        <child>
-                          <placeholder/>
-                        </child>
-                        <child type="tab">
-                          <placeholder/>
-                        </child>
-                        <child>
-                          <placeholder/>
-                        </child>
-                        <child type="tab">
-                          <placeholder/>
-                        </child>
-                        <child>
-                          <placeholder/>
-                        </child>
-                        <child type="tab">
-                          <placeholder/>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">-1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkBox">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="valign">start</property>
-                        <child>
-                          <object class="GtkCheckButton" id="autosave_checkbutton">
-                            <property name="label" translatable="yes">Autosave</property>
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
-                            <property name="receives_default">False</property>
-                            <property name="draw_indicator">True</property>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">True</property>
-                            <property name="position">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkFileChooserButton" id="autosavedir_button">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="valign">start</property>
-                            <property name="action">select-folder</property>
-                            <property name="preview_widget_active">False</property>
-                            <property name="title" translatable="yes">Select a Save Folder</property>
-                          </object>
-                          <packing>
-                            <property name="expand">True</property>
-                            <property name="fill">True</property>
-                            <property name="position">1</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkEntry" id="autosavename">
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
-                            <property name="has_tooltip">True</property>
-                            <property name="valign">start</property>
-                            <property name="max_length">32</property>
-                            <property name="invisible_char">●</property>
-                            <property name="text" translatable="yes">filename</property>
-                            <property name="caps_lock_warning">False</property>
-                            <property name="primary_icon_stock">gtk-file</property>
-                            <property name="primary_icon_activatable">False</property>
-                            <property name="secondary_icon_activatable">False</property>
-                            <property name="primary_icon_tooltip_text" translatable="yes">File name</property>
-                            <property name="primary_icon_tooltip_markup" translatable="yes">File name</property>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">True</property>
-                            <property name="position">2</property>
-                          </packing>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                  </object>
-                </child>
-                <child type="tab">
-                  <object class="GtkLabel" id="label2">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">Plot</property>
-                  </object>
-                  <packing>
-                    <property name="tab_fill">False</property>
-                  </packing>
-                </child>
-                <child>
-                  <placeholder/>
-                </child>
-                <child type="tab">
-                  <placeholder/>
-                </child>
-                <child>
-                  <placeholder/>
-                </child>
-                <child type="tab">
-                  <placeholder/>
-                </child>
-              </object>
-              <packing>
-                <property name="resize">True</property>
-                <property name="shrink">True</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <child>
-              <object class="GtkStatusbar" id="statusbar">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="spacing">2</property>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="pack_type">end</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="label1">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">Serial Port:</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="padding">5</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkComboBox" id="serial_combobox">
-                <property name="width_request">128</property>
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="halign">start</property>
-                <property name="model">serial_liststore</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">2</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="serial_connect">
-                <property name="label">gtk-connect</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="use_stock">True</property>
-                <signal name="clicked" handler="on_serial_connect_clicked" swapped="no"/>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">3</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="pmt_mode">
-                <property name="label" translatable="yes">Connect (PMT mode)</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <signal name="clicked" handler="on_serial_connect_clicked" swapped="no"/>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">4</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="serial_disconnect">
-                <property name="label">gtk-disconnect</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="use_stock">True</property>
-                <signal name="clicked" handler="on_serial_disconnect_clicked" swapped="no"/>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">5</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="serial_refresh">
-                <property name="label">gtk-refresh</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="use_stock">True</property>
-                <signal name="clicked" handler="on_serial_refresh_clicked" swapped="no"/>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">6</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="ocp_disp">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="halign">start</property>
-                <property name="label" translatable="yes">OCP:</property>
-                <property name="single_line_mode">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="padding">3</property>
-                <property name="position">7</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">2</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-  </object>
-  <object class="GtkAdjustment" id="updatesamples_adj">
-    <property name="upper">100</property>
-    <property name="value">5</property>
-    <property name="step_increment">1</property>
-    <property name="page_increment">10</property>
-  </object>
-</interface>
diff --git a/dstat_interface/core/interface/exp_int.py b/dstat_interface/core/interface/exp_int.py
deleted file mode 100755
index a4aba0e..0000000
--- a/dstat_interface/core/interface/exp_int.py
+++ /dev/null
@@ -1,540 +0,0 @@
-#!/usr/bin/env python
-#     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/>.
-from __future__ import division, absolute_import, print_function, unicode_literals
-
-import os
-import sys
-import logging
-
-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)
-
-from ..dstat import comm, state
-from ..experiments import (cal, chronoamp, cv, experiment_template,
-                           idle, lsv, pot, swv)
-import __main__
-from ..errors import InputError, VarError
-
-logger = logging.getLogger(__name__)
-
-mod_dir = os.path.dirname(os.path.abspath(__file__))
-
-
-class ExpInterface(GObject.Object):
-    """Generic experiment interface class. Should be subclassed to implement
-    experiment interfaces by populating self.entry. Override class attributes
-    to set id and experiment class to run.
-    """
-    __gsignals__ = {
-        b'run_utility': (GObject.SIGNAL_RUN_FIRST, None, ()),
-        b'done_utility': (GObject.SIGNAL_RUN_FIRST, None, ())
-    }
-
-    id = None
-    experiment = None
-    
-    def __init__(self, glade_path):
-        super(ExpInterface, self).__init__()
-        self.builder = Gtk.Builder()
-        self.builder.add_from_file(glade_path)
-        self.builder.connect_signals(self)
-        self.entry = {} # to be used only for str parameters
-        self._params = None
-
-    def get_experiment(self, parameters):
-        return self.__class__.experiment(parameters)
-
-    def _fill_params(self):
-        self._params = dict.fromkeys(self.entry.keys())
-    
-    @property
-    def params(self):
-        """Dict of parameters"""
-        if self._params is None:
-            self._fill_params()
-        self._get_params()
-        return self._params
-    
-    def _get_params(self):
-        """Updates self._params from UI."""
-        for i in self.entry:
-            self._params[i] = self.entry[i].get_text()
-    
-    @params.setter
-    def params(self, params):
-        if self._params is None:
-            self._fill_params()
-        for i in self._params:
-            try:
-                self._params[i] = params[i]
-            except KeyError as e:
-                logger.warning("Invalid parameter key: %s" % e)
-        self._set_params()
-
-    def _set_params(self):
-        """Updates UI with new parameters."""
-        for i in self.entry:
-            self.entry[i].set_text(self._params[i])
-            
-    def get_window(self):
-        return self.builder.get_object('scrolledwindow1')
-    def on_run_utility(self, data=None):
-      self.emit('run_utility')
-    def on_done_utility(self, data=None):
-      self.emit('done_utility')
-
-
-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)
-    """
-    id = 'cae'
-    experiment = chronoamp.Chronoamp
-    def __init__(self):
-        """Extends superclass method to support treeview."""
-        super(Chronoamp, self).__init__(os.path.join(mod_dir, 'chronoamp.glade'))
-        
-        self.name = "Chronoamperometry"
-        
-        self.statusbar = self.builder.get_object('statusbar')
-        self.model = self.builder.get_object('ca_list')
-        self.treeview = self.builder.get_object('treeview')
-        self.treeview.set_fixed_height_mode(False)
-        
-        for i, column_title in enumerate(["Potential", "Time"]):
-            renderer = Gtk.CellRendererText()
-            column = Gtk.TreeViewColumn(column_title, renderer, text=i)
-            column.set_expand(True)
-            self.treeview.append_column(column)
-        
-        self.selection = self.treeview.get_selection()
-        self.selection.set_mode(Gtk.SelectionMode.MULTIPLE)
-
-    def _fill_params(self):
-        super(Chronoamp, self)._fill_params()
-        self._params['potential'] = []
-        self._params['time'] = []
-
-    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 not state.board_instance.test_mv(potential):
-                raise ValueError("Potential out of range")
-            if not state.board_instance.test_s(time):
-                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):
-        """Updates self._params from UI. Overrides superclass method."""
-
-        self._params['potential'] = [int(r[0]) for r in self.model]
-        self._params['time'] = [int(r[1]) for r in self.model]
-        
-    def _set_params(self):
-        """Updates UI from self._params. Overrides superclass method."""
-        
-        self.model.clear()
-        
-        table = zip(self._params['potential'], self._params['time'])
-        
-        for i in table:
-            self.model.append(i)
-    
-class LSV(ExpInterface):
-    """Experiment class for LSV."""
-    id = 'lsv'
-    experiment = lsv.LSVExp
-    def __init__(self):
-        """Adds entry listings to superclass's self.entry dict"""
-        super(LSV, self).__init__(os.path.join(mod_dir, 'lsv.glade'))
-        self.name = "Linear Sweep Voltammetry"
-        
-        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."""
-    id = 'cve'
-    experiment = cv.CVExp
-    def __init__(self):
-        """Adds entry listings to superclass's self.entry dict"""
-        super(CV, self).__init__(os.path.join(mod_dir, 'cv.glade'))
-        self.name = "Cyclic Voltammetry"
-        
-        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."""
-    id = 'swv'
-    experiment = swv.SWVExp
-    def __init__(self):
-        """Adds entry listings to superclass's self.entry dict"""
-        super(SWV, self).__init__(os.path.join(mod_dir, 'swv.glade'))
-        self.name = "Square Wave Voltammetry"
-        
-        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 _fill_params(self):
-        super(SWV, self)._fill_params()
-        
-        self._params['cyclic_true'] = False
-    
-    def _get_params(self):
-        """Updates self._params from UI."""
-        super(SWV, self)._get_params()
-        
-        self._params['cyclic_true'] = self.builder.get_object(    
-                           'cyclic_checkbutton').get_active()
-    
-    def _set_params(self):
-        """Updates UI with new parameters."""
-        super(SWV, self)._set_params()
-    
-        self.builder.get_object('cyclic_checkbutton').set_active(
-                                                self._params['cyclic_true']) 
-        
-class DPV(ExpInterface):
-    """Experiment class for DPV."""
-    id = 'dpv'
-    experiment = swv.DPVExp
-    def __init__(self):
-        """Adds entry listings to superclass's self.entry dict"""
-        super(DPV, self).__init__(os.path.join(mod_dir, 'dpv.glade'))
-        
-        self.name = "Differential Pulse Voltammetry"
-        
-        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."""
-#     id = 'acv'
-#     def __init__(self):
-#         """Adds entry listings to superclass's self.entry dict"""
-#         super(ACV, self).__init__('interface/acv.glade')
-#         self.name = "AC Voltammetry"
-#
-#         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."""
-    id = 'pde'
-    experiment = chronoamp.PDExp
-    def __init__(self):
-        """Adds entry listings to superclass's self.entry dict"""
-        super(PD, self).__init__(os.path.join(mod_dir, 'pd.glade'))
-        self.name = "Photodiode/PMT"
-        
-        
-        self.entry['time'] = self.builder.get_object('time_entry')
-        self.entry['sync_freq'] = self.builder.get_object('sync_freq')
-        self.entry['fft_start'] = self.builder.get_object('fft_entry')
-        self.entry['fft_int'] = self.builder.get_object('fft_int_entry')
-        
-        self.buttons = map(self.builder.get_object,
-                           ['light_button', 'threshold_button'])
-                           
-        self.shutter_buttons = map(
-            self.builder.get_object,
-            ['sync_button', 'sync_freq', 'fft_label', 'fft_entry', 'fft_label2',
-                'fft_int_entry']
-            )
-        
-        bool_keys = ['interlock_true', 'shutter_true', 'sync_true']
-        bool_cont = map(self.builder.get_object,
-                             ['interlock_button',
-                              'shutter_button',
-                              'sync_button']
-                        )
-        self.bool = dict(zip(bool_keys, bool_cont))
-
-    def _fill_params(self):
-        super(PD, self)._fill_params()
-        
-        for i in self.bool:
-            self._params[i] = self.bool[i].get_active()
-        self._params['voltage'] = 0
-
-    def _get_params(self):
-        """Updates self._params from UI."""
-        super(PD, self)._get_params()
-        
-        for i in self.bool:
-            self._params[i] = self.bool[i].get_active()
-        
-        self._params['voltage'] = self.builder.get_object(         
-                                     'voltage_adjustment').get_value()
-    
-    def _set_params(self):
-        """Updates UI with new parameters."""
-        super(PD, self)._set_params()
-
-        for i in self.bool:
-            self.bool[i].set_active(self._params[i])
-        
-        self.builder.get_object('voltage_adjustment').set_value(
-                                                          self._params['voltage'])
-        
-    def on_light_button_clicked(self, data=None):
-        __main__.MAIN.on_pot_stop_clicked()
-        __main__.MAIN.stop_ocp()
-        
-        for i in self.buttons:
-            i.set_sensitive(False)
-            
-        try:
-            self.builder.get_object('light_label').set_text(str(
-                dstat_comm.read_light_sensor()))
-            comm.read_settings()
-            state.settings['tcs_enabled'][1] = '1' # Make sure TCS enabled
-            comm.write_settings()
-            
-            self.builder.get_object('threshold_entry').set_text(str(
-                                    state.settings['tcs_clear_threshold'][1]))   
-            __main__.MAIN.start_ocp()
-            
-        finally:
-            GObject.timeout_add(700, restore_buttons, self.buttons)
-        
-    def on_threshold_button_clicked(self, data=None):
-        __main__.MAIN.on_pot_stop_clicked()
-        __main__.MAIN.stop_ocp()
-        
-        for i in self.buttons:
-            i.set_sensitive(False)
-            
-        try:
-            state.settings['tcs_clear_threshold'][1] = self.builder.get_object(
-                                                    'threshold_entry').get_text()
-            comm.write_settings()
-            comm.read_settings()
-            self.builder.get_object('threshold_entry').set_text(
-                                str(state.settings['tcs_clear_threshold'][1]))   
-            __main__.MAIN.start_ocp()
-        
-        finally:
-            GObject.timeout_add(700, restore_buttons, self.buttons)
-            
-    def on_shutter_button_toggled(self, widget):
-        if self.bool['shutter_true'].get_active():
-            for i in self.shutter_buttons:
-                i.set_sensitive(True)
-        else:
-            for i in self.shutter_buttons:
-                i.set_sensitive(False)
-                
-        
-class POT(ExpInterface):
-    """Experiment class for Potentiometry."""
-    id = 'pot'
-    experiment = pot.PotExp
-    def __init__(self):
-        """Adds entry listings to superclass's self.entry dict"""
-        super(POT, self).__init__(os.path.join(mod_dir, 'potexp.glade'))
-        self.name = "Potentiometry"
-        
-        self.entry['time'] = self.builder.get_object('time_entry')
-        
-class CAL(ExpInterface):
-    """Experiment class for Calibrating gain."""
-    id = 'cal'
-    experiment = cal.CALExp
-    def __init__(self):
-        """Adds entry listings to superclass's self.entry dict"""
-        super(CAL, self).__init__(os.path.join(mod_dir, 'calib.glade'))
-        self.name = "Calilbration"
-        
-        self.entry['time'] = self.builder.get_object('time_entry')
-        self.entry['R100'] = self.builder.get_object('100_entry')
-        self.entry['R3k'] = self.builder.get_object('3k_entry')
-        self.entry['R30k'] = self.builder.get_object('30k_entry')
-        self.entry['R300k'] = self.builder.get_object('300k_entry')
-        self.entry['R3M'] = self.builder.get_object('3M_entry')
-        self.entry['R30M'] = self.builder.get_object('30M_entry')
-        self.entry['R100M'] = self.builder.get_object('100M_entry')
-        
-        self.buttons = [self.builder.get_object('read_button'),
-                        self.builder.get_object('write_button'),
-                        self.builder.get_object('measure_button')]
-        
-    def on_read_button_clicked(self, data=None):        
-        for i in self.buttons:
-            i.set_sensitive(False)
-        
-        try:
-            __main__.MAIN.on_pot_stop_clicked()
-            __main__.MAIN.stop_ocp()
-            comm.read_settings()
-    
-            self.entry['R100'].set_text(str(
-                state.settings['r100_trim'][1]))
-            self.entry['R3k'].set_text(str(
-                state.settings['r3k_trim'][1]))
-            self.entry['R30k'].set_text(str(
-                state.settings['r30k_trim'][1]))
-            self.entry['R300k'].set_text(str(
-                state.settings['r300k_trim'][1]))
-            self.entry['R3M'].set_text(str(
-                state.settings['r3M_trim'][1]))
-            self.entry['R30M'].set_text(str(
-                state.settings['r30M_trim'][1]))
-            self.entry['R100M'].set_text(str(
-                state.settings['r100M_trim'][1]))
-    
-            __main__.MAIN.start_ocp()
-            
-        finally:
-            GObject.timeout_add(700, restore_buttons, self.buttons)
-        
-    def on_write_button_clicked(self, data=None):
-        for i in self.buttons:
-            i.set_sensitive(False)
-        
-        try:
-            __main__.MAIN.on_pot_stop_clicked()
-            __main__.MAIN.stop_ocp()
-            
-            state.settings['r100_trim'][1] = self.entry['R100'].get_text()
-            state.settings['r3k_trim'][1] = self.entry['R3k'].get_text()
-            state.settings['r30k_trim'][1] = self.entry['R30k'].get_text()
-            state.settings['r300k_trim'][1] = self.entry['R300k'].get_text()
-            state.settings['r3M_trim'][1] = self.entry['R3M'].get_text()
-            state.settings['r30M_trim'][1] = self.entry['R30M'].get_text()
-            state.settings['r100M_trim'][1] = self.entry['R100M'].get_text()
-            comm.write_settings()        
-                                
-            __main__.MAIN.start_ocp()
-            
-        finally:
-            GObject.timeout_add(700, restore_buttons, self.buttons)
-                
-    def on_measure_button_clicked(self, data=None):
-        if int(self.entry['time'].get_text()) <= 0 or int(self.entry['time'].get_text()) > 65535:
-            logger.error("ERR: Time out of range")
-            return
-        
-        for i in self.buttons:
-            i.set_sensitive(False)
-        
-        try:
-            __main__.MAIN.stop_ocp()
-            __main__.MAIN.spinner.start()
-            
-            offset = cal.measure_offset(self.params['time'])
-            
-            for i in offset:
-                logger.info("{} {}".format(i, str(-offset[i])))
-                state.settings[i][1] = str(-offset[i])
-            
-            self.entry['R100'].set_text(str(
-                state.settings['r100_trim'][1]))
-            self.entry['R3k'].set_text(str(
-                state.settings['r3k_trim'][1]))
-            self.entry['R30k'].set_text(str(
-                state.settings['r30k_trim'][1]))
-            self.entry['R300k'].set_text(str(
-                state.settings['r300k_trim'][1]))
-            self.entry['R3M'].set_text(str(
-                state.settings['r3M_trim'][1]))
-            self.entry['R30M'].set_text(str(
-                state.settings['r30M_trim'][1]))
-            self.entry['R100M'].set_text(str(
-                state.settings['r100M_trim'][1]))
-            __main__.MAIN.start_ocp()
-        
-        finally:
-            GObject.timeout_add(700, restore_buttons, self.buttons)
-            __main__.MAIN.spinner.stop()
-
-
-def restore_buttons(buttons):
-    """ Should be called with GObject callback """
-    for i in buttons:
-        i.set_sensitive(True)
-        
-    return False
\ No newline at end of file
diff --git a/dstat_interface/core/interface/exp_window.py b/dstat_interface/core/interface/exp_window.py
deleted file mode 100755
index 61906b5..0000000
--- a/dstat_interface/core/interface/exp_window.py
+++ /dev/null
@@ -1,129 +0,0 @@
-#!/usr/bin/env python
-#     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 inspect
-import logging
-from collections import OrderedDict
-
-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)
-
-from . import exp_int
-
-logger = logging.getLogger(__name__)
-
-
-class Experiments(GObject.Object):
-    __gsignals__ = {
-        'run_utility': (GObject.SIGNAL_RUN_FIRST, None, ()),
-        'done_utility': (GObject.SIGNAL_RUN_FIRST, None, ())
-    }
-
-    def __init__(self, builder):
-        super(Experiments,self).__init__()
-        self.builder = builder
-        self.builder.connect_signals(self)
-        
-        # (experiment index in UI, experiment)
-            
-        classes = {c.id : c()  # Make class instances
-                        for _, c in inspect.getmembers(exp_int, inspect.isclass)
-                        if issubclass(c, exp_int.ExpInterface) 
-                            and c is not exp_int.ExpInterface
-                        }
-        
-        self.classes = OrderedDict(sorted(classes.items()))
-
-        # fill exp_section
-        exp_section = self.builder.get_object('exp_section_box')
-        self.containers = {}
-        
-        for key, c in self.classes.items():
-            c.connect('run_utility', self.on_run_utility)
-            c.connect('done_utility', self.on_done_utility)
-            self.containers[key] = c.get_window()
-
-        for key in self.containers:
-            try:
-                self.containers[key].get_parent().remove(self.containers[key])
-            except AttributeError:
-                pass
-                
-            exp_section.add(self.containers[key])
-        
-        self.expcombobox = self.builder.get_object('expcombobox')
-        self.expcombobox.connect('changed', self.on_expcombobox_changed)
-        
-        for c in self.classes.values():
-            self.expcombobox.append(id=c.id, text=c.name)
-
-    def on_run_utility(self, data=None):
-        self.emit('run_utility')
-    
-    def on_done_utility(self, data=None):
-        self.emit('done_utility')
-
-    def on_expcombobox_changed(self, data=None):
-        """Change the experiment window when experiment box changed."""
-        self.set_exp(self.expcombobox.get_active_id())
-    
-    def setup_exp(self, parameters):
-        """Takes parameters.
-        Returns experiment instance.
-        """
-        exp = self.classes[self.expcombobox.get_active_id()]
-        try:
-            exp.param_test(parameters)
-        except AttributeError:
-            logger.warning(
-                "Experiment {} has no defined parameter test.".format(
-                    exp.name)
-                )
-        return exp.get_experiment(parameters)
-
-    def hide_exps(self):
-        for key in self.containers:
-            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
-        """
-        self.hide_exps()
-        self.containers[selection].show()
-        self.selected_exp = selection
-        
-        return True
-        
-    def get_params(self, experiment):
-        return self.classes[experiment].params
-    
-    def set_params(self, experiment, parameters):
-        try:
-            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
diff --git a/dstat_interface/core/interface/hw_info.py b/dstat_interface/core/interface/hw_info.py
deleted file mode 100755
index 3a8228b..0000000
--- a/dstat_interface/core/interface/hw_info.py
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/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
diff --git a/dstat_interface/core/interface/lsv.glade b/dstat_interface/core/interface/lsv.glade
deleted file mode 100644
index 404e1e4..0000000
--- a/dstat_interface/core/interface/lsv.glade
+++ /dev/null
@@ -1,307 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkWindow" id="window1">
-    <property name="can_focus">False</property>
-    <child>
-      <object class="GtkScrolledWindow" id="scrolledwindow1">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <child>
-          <object class="GtkViewport" id="viewport1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="shadow_type">none</property>
-            <child>
-              <object class="GtkBox" id="vbox1">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkFrame" id="frame1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">out</property>
-                    <child>
-                      <object class="GtkAlignment" id="alignment1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="bottom_padding">5</property>
-                        <property name="left_padding">5</property>
-                        <property name="right_padding">5</property>
-                        <child>
-                          <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row_homogeneous">True</property>
-                            <property name="column_homogeneous">True</property>
-                            <child>
-                              <object class="GtkLabel" id="label3">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Potential (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label4">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Time (s)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label5">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label6">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Cleaning</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label7">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Deposition</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="clean_mV">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="dep_mV">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="dep_s">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="clean_s">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                    <child type="label">
-                      <object class="GtkLabel" id="label1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Preconditioning</property>
-                        <property name="use_markup">True</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="padding">2</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkFrame" id="frame2">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">out</property>
-                    <child>
-                      <object class="GtkAlignment" id="alignment2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="bottom_padding">5</property>
-                        <property name="left_padding">5</property>
-                        <property name="right_padding">5</property>
-                        <child>
-                          <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row_homogeneous">True</property>
-                            <property name="column_homogeneous">True</property>
-                            <child>
-                              <object class="GtkLabel" id="label8">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Start (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label9">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Stop (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label10">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Slope (mV/s)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="start_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="stop_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="slope_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                    <child type="label">
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Experiment</property>
-                        <property name="use_markup">True</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="padding">2</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>
diff --git a/dstat_interface/core/interface/pd.glade b/dstat_interface/core/interface/pd.glade
deleted file mode 100644
index c39930e..0000000
--- a/dstat_interface/core/interface/pd.glade
+++ /dev/null
@@ -1,348 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkAdjustment" id="voltage_adjustment">
-    <property name="upper">3000</property>
-    <property name="step_increment">1</property>
-    <property name="page_increment">10</property>
-  </object>
-  <object class="GtkWindow" id="window1">
-    <property name="can_focus">False</property>
-    <child>
-      <object class="GtkScrolledWindow" id="scrolledwindow1">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <child>
-          <object class="GtkViewport" id="viewport1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="shadow_type">none</property>
-            <child>
-              <object class="GtkBox" id="vbox1">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="column_homogeneous">True</property>
-                    <child>
-                      <object class="GtkEntry" id="time_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label8">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Bias Voltage (mV)</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Measurement Time (s)</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkSpinButton" id="spinbutton1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="max_length">4</property>
-                        <property name="invisible_char">●</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                        <property name="adjustment">voltage_adjustment</property>
-                        <property name="climb_rate">1</property>
-                        <property name="snap_to_ticks">True</property>
-                        <property name="numeric">True</property>
-                        <property name="wrap">True</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Ambient Light Interlock</property>
-                        <attributes>
-                          <attribute name="weight" value="bold"/>
-                        </attributes>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">3</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkCheckButton" id="interlock_button">
-                        <property name="label" translatable="yes">Enable interlock</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="active">True</property>
-                        <property name="draw_indicator">True</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">3</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkButton" id="threshold_button">
-                        <property name="label" translatable="yes">Set threshold</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">True</property>
-                        <property name="halign">center</property>
-                        <signal name="clicked" handler="on_threshold_button_clicked" swapped="no"/>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">4</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="threshold_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">4</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkButton" id="light_button">
-                        <property name="label" translatable="yes">Refresh light measurement</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">True</property>
-                        <property name="halign">center</property>
-                        <signal name="clicked" handler="on_light_button_clicked" swapped="no"/>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">5</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="light_label">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">0</property>
-                        <property name="justify">center</property>
-                        <property name="track_visited_links">False</property>
-                        <attributes>
-                          <attribute name="weight" value="medium"/>
-                        </attributes>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">5</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label3">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Electromechanical Shutter</property>
-                        <attributes>
-                          <attribute name="weight" value="bold"/>
-                        </attributes>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">7</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkCheckButton" id="sync_button">
-                        <property name="label" translatable="yes">Synchronous Detection (Hz)</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="halign">center</property>
-                        <property name="draw_indicator">True</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">8</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="sync_freq">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">8</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkCheckButton" id="shutter_button">
-                        <property name="label" translatable="yes">Enable Shutter</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="active">True</property>
-                        <property name="draw_indicator">True</property>
-                        <signal name="toggled" handler="on_shutter_button_toggled" swapped="no"/>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">7</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="fft_label">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Start FFT after (s)</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">9</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="fft_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="max_length">2</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text">1</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">9</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="fft_label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">FFT Integral bandwidth (Hz)</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">10</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="fft_int_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="max_length">6</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">8</property>
-                        <property name="text">1</property>
-                        <property name="xalign">1</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">10</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkSeparator">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="margin_top">5</property>
-                        <property name="margin_bottom">5</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">2</property>
-                        <property name="width">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkSeparator">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="margin_top">5</property>
-                        <property name="margin_bottom">5</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">6</property>
-                        <property name="width">2</property>
-                      </packing>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label13">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <attributes>
-                      <attribute name="weight" value="bold"/>
-                    </attributes>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>
diff --git a/dstat_interface/core/interface/plot.py b/dstat_interface/core/interface/plot.py
deleted file mode 100755
index 7e87c41..0000000
--- a/dstat_interface/core/interface/plot.py
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/usr/bin/env python
-#     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/>.
-
-"""
-Creates data plot.
-"""
-try:
-    import gi
-    gi.require_version('Gtk', '3.0')
-    from gi.repository import Gtk
-except ImportError:
-    print "ERR: GTK not available"
-    sys.exit(1)
-    
-from matplotlib.figure import Figure
-
-from matplotlib.backends.backend_gtk3agg \
-    import FigureCanvasGTK3Agg as FigureCanvas
-from matplotlib.backends.backend_gtk3 \
-    import NavigationToolbar2GTK3 as NavigationToolbar
-try:
-    import seaborn as sns
-except ImportError:
-    pass
-    
-from numpy import sin, linspace, pi, mean, trapz
-from scipy import fft, arange
-
-def plotSpectrum(y,Fs):
-    """
-    Plots a Single-Sided Amplitude Spectrum of y(t)
-    """    
-    y = y-mean(y)
-    n = len(y) # length of the signal
-    k = arange(n)
-    T = n/Fs
-    frq = k/T # two sides frequency range
-    frq = frq[range(n/2)] # one side frequency range
-    Y = fft(y)/n # fft computing and normalization
-    Y = abs(Y[range(n/2)])
-    
-    return (frq, Y)
-
-def integrateSpectrum(x, y, target, bandwidth):
-    """
-    Returns integral of range of bandwidth centered on target (both in Hz).
-    """
-    j = 0
-    k = len(x)
-    
-    for i in range(len(x)):
-        if x[i] >= target-bandwidth/2:
-            j = i
-            break
-            
-    for i in range(j,len(x)):
-        if x[i] >= target+bandwidth/2:
-            k = i
-            break
-        
-    return trapz(y=y[j:k], x=x[j:k])
-    
-def findBounds(y):
-    start_index = 0;
-    stop_index = len(y)-1;
-    
-    for i in range(len(y)):
-        if (y[i] <= mean(y) and y[i+1] > mean(y)):
-            start_index = i
-            break
-    
-    for i in range(len(y)):
-        if (y[-(i+1)] <= mean(y) and y[-i] > mean(y)):
-            stop_index = len(y)-1-i  # len(y) is last index + 1
-            break
-    
-    return (start_index, stop_index)
-
diff --git a/dstat_interface/core/interface/plot_ui.py b/dstat_interface/core/interface/plot_ui.py
deleted file mode 100644
index b0013e8..0000000
--- a/dstat_interface/core/interface/plot_ui.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import logging
-logger = logging.getLogger(__name__)
-
-try:
-    import gi
-    gi.require_version('Gtk', '3.0')
-    from gi.repository import Gtk
-except ImportError:
-    print "ERR: GTK not available"
-    sys.exit(1)
-    
-from matplotlib.backends.backend_gtk3 \
-    import NavigationToolbar2GTK3 as NavigationToolbar
-    
-def clear_notebook(notebook):
-    for pages in range(notebook.get_n_pages()):
-        notebook.remove_page(0)
-        
-def add_exp_to_notebook(notebook, exp):
-    for plot in exp.plots:
-        label = Gtk.Label.new(plot.name)
-        notebook.append_page(plot.box, label)
-        plot.box.show_all()
-
-def replace_notebook_exp(notebook, exp, window):
-    clear_notebook(notebook)
-    add_exp_to_notebook(notebook, exp)
-    add_navigation_toolbars(exp, window)
-
-def add_navigation_toolbars(exp, window):
-    for plot in exp.plots:
-        toolbar = NavigationToolbar(plot.canvas, window)
-        plot.box.pack_start(toolbar, expand=False, fill=False, padding=0)
\ No newline at end of file
diff --git a/dstat_interface/core/interface/potexp.glade b/dstat_interface/core/interface/potexp.glade
deleted file mode 100644
index d4ec1e6..0000000
--- a/dstat_interface/core/interface/potexp.glade
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkListStore" id="ca_list">
-    <columns>
-      <!-- column-name millivolts -->
-      <column type="gint"/>
-      <!-- column-name seconds -->
-      <column type="guint"/>
-    </columns>
-  </object>
-  <object class="GtkWindow" id="window1">
-    <property name="can_focus">False</property>
-    <property name="default_width">300</property>
-    <property name="default_height">500</property>
-    <child>
-      <object class="GtkScrolledWindow" id="scrolledwindow1">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <child>
-          <object class="GtkViewport" id="viewport1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="shadow_type">none</property>
-            <child>
-              <object class="GtkBox" id="vbox1">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="row_homogeneous">True</property>
-                    <property name="column_homogeneous">True</property>
-                    <child>
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Time (s)</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="time_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="invisible_char">●</property>
-                        <property name="width_chars">5</property>
-                        <property name="text" translatable="yes">0</property>
-                        <property name="xalign">1</property>
-                        <property name="truncate_multiline">True</property>
-                        <property name="caps_lock_warning">False</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">Connect the electrodes to the RE input and the W_SHIELD connectors.
-The ADC's PGA can be used to amplify the input signal, but note that the plot's y-axis is only correct for PGA 2x.</property>
-                    <property name="wrap">True</property>
-                    <property name="width_chars">30</property>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>
diff --git a/dstat_interface/core/interface/save.py b/dstat_interface/core/interface/save.py
deleted file mode 100755
index 44a7884..0000000
--- a/dstat_interface/core/interface/save.py
+++ /dev/null
@@ -1,226 +0,0 @@
-#!/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/>.
-
-from __future__ import division, absolute_import, print_function, unicode_literals
-
-import io
-import os
-import logging
-logger = logging.getLogger(__name__)
-
-
-try:
-    import gi
-    gi.require_version('Gtk', '3.0')
-    from gi.repository import Gtk
-except ImportError:
-    print("ERR: GTK not available")
-    sys.exit(1)
-import numpy as np
-
-from ..errors import InputError, VarError
-from ..params import save_params, load_params
-
-def manSave(current_exp):
-    fcd = Gtk.FileChooserDialog("Save…", None, Gtk.FileChooserAction.SAVE,
-                                (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
-                                 Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
-    
-    filters = [Gtk.FileFilter()]
-    filters[0].set_name("Tab-separated Text (.txt)")
-    filters[0].add_pattern("*.txt")
-
-    fcd.set_do_overwrite_confirmation(True)
-    for i in filters:
-        fcd.add_filter(i)
-
-    response = fcd.run()
-
-    if response == Gtk.ResponseType.OK:
-        path = fcd.get_filename().decode("utf-8")
-        logger.info("Selected filepath: %s", path)
-        filter_selection = fcd.get_filter().get_name().decode("utf-8")
-
-        if filter_selection.endswith("(.txt)"):
-            save_text(current_exp, path)
-            
-        fcd.destroy()
-
-    elif response == Gtk.ResponseType.CANCEL:
-        fcd.destroy()
-
-def plot_save_dialog(plots):
-    fcd = Gtk.FileChooserDialog("Save Plot…", None,
-                                Gtk.FileChooserAction.SAVE,
-                                (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
-                                 Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
-
-    filters = [Gtk.FileFilter()]
-    filters[0].set_name("Portable Document Format (.pdf)")
-    filters[0].add_pattern("*.pdf")
-    filters.append(Gtk.FileFilter())
-    filters[1].set_name("Portable Network Graphics (.png)")
-    filters[1].add_pattern("*.png")
-
-    fcd.set_do_overwrite_confirmation(True)
-    for i in filters:
-        fcd.add_filter(i)
-
-    response = fcd.run()
-
-    if response == Gtk.ResponseType.OK:
-        path = fcd.get_filename().decode("utf-8")
-        logger.info("Selected filepath: %r", path)
-        filter_selection = fcd.get_filter().get_name().decode("utf-8")
-        
-        if filter_selection.endswith("(.pdf)"):
-            if not path.endswith(".pdf"):
-                path += ".pdf"
-
-        elif filter_selection.endswith("(.png)"):
-            if not path.endswith(".png"):
-                path += ".png"
-        
-        save_plot(plots, path)
-
-        fcd.destroy()
-
-    elif response == Gtk.ResponseType.CANCEL:
-        fcd.destroy()
-
-
-def man_param_save(window):
-    fcd = Gtk.FileChooserDialog("Save Parameters…",
-                                None,
-                                Gtk.FileChooserAction.SAVE,
-                                (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
-                                 Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
-
-    filters = [Gtk.FileFilter()]
-    filters[0].set_name("Parameter File (.yml)")
-    filters[0].add_pattern("*.yml")
-
-    fcd.set_do_overwrite_confirmation(True)
-    for i in filters:
-        fcd.add_filter(i)
-
-    response = fcd.run()
-
-    if response == Gtk.ResponseType.OK:
-        path = fcd.get_filename().decode("utf-8")
-        logger.info("Selected filepath: %s", path)
-
-        if not path.endswith(".yml"):
-            path += '.yml'
-
-        save_params(window, path)
-
-        fcd.destroy()
-
-    elif response == Gtk.ResponseType.CANCEL:
-        fcd.destroy()
-
-def man_param_load(window):
-    fcd = Gtk.FileChooserDialog("Load Parameters…",
-                                None,
-                                Gtk.FileChooserAction.OPEN,
-                                (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
-                                 Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
-
-    filters = [Gtk.FileFilter()]
-    filters[0].set_name("Parameter File (.yml)")
-    filters[0].add_pattern("*.yml")
-
-    for i in filters:
-        fcd.add_filter(i)
-
-    response = fcd.run()
-
-    if response == Gtk.ResponseType.OK:
-        path = fcd.get_filename().decode("utf-8")
-        logger.info("Selected filepath: %s", path)
-
-        load_params(window, path)
-
-        fcd.destroy()
-
-    elif response == Gtk.ResponseType.CANCEL:
-        fcd.destroy()
-
-def autoSave(exp, path, name):
-    if name == "":
-        name = "file"
-
-    path += '/'
-    path += name
-     
-    save_text(exp, path)
-
-def autoPlot(exp, path, name):
-    if name == "":
-        name = "file"
-        
-    path += '/'
-    path += name
-
-    if not (path.endswith(".pdf") or path.endswith(".png")):
-        path += ".pdf"
-
-    save_plot(exp, path)
-
-def save_text(exp, path):
-    savestrings = exp.get_save_strings()
-    path = path.rstrip('.txt')
-    
-    num = ''
-    j = 0
-    
-    for key, text in savestrings.items(): # Test for existing files of any kind
-        while os.path.exists("{}{}-{}.txt".format(path, num, key)):
-            j += 1
-            num = j
-            
-    save_path = "{}{}".format(path, num)
-    
-    for key, text in savestrings.items():   
-        with open('{}-{}.txt'.format(save_path, key), 'w') as f:
-            f.write(text)
-        
-def save_plot(exp, path):
-    """Saves everything in exp.plots to path. Appends a number for duplicates.
-    If no file extension or unknown, uses pdf.
-    """
-    name, _sep, ext = path.rpartition('.')
-    if _sep == '':
-        name = ext
-        ext = 'pdf'
-    
-    num = ''
-    j = 0
-    
-    for i in exp.plots: # Test for any existing files
-        plot_type = '_'.join(i.name.lower().split())
-        while os.path.exists("{}{}-{}.{}".format(name, num, plot_type, ext)):
-            j += 1
-            num = j
-    
-    for i in exp.plots: # save data
-        plot_type = '_'.join(i.name.lower().split())
-        i.figure.savefig("{}{}-{}.{}".format(name, num, plot_type, ext))
\ No newline at end of file
diff --git a/dstat_interface/core/interface/swv.glade b/dstat_interface/core/interface/swv.glade
deleted file mode 100644
index 0a1e326..0000000
--- a/dstat_interface/core/interface/swv.glade
+++ /dev/null
@@ -1,433 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <object class="GtkWindow" id="window1">
-    <property name="can_focus">False</property>
-    <child>
-      <object class="GtkScrolledWindow" id="scrolledwindow1">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <child>
-          <object class="GtkViewport" id="viewport1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="shadow_type">none</property>
-            <child>
-              <object class="GtkBox" id="vbox1">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkFrame" id="frame1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">out</property>
-                    <child>
-                      <object class="GtkAlignment" id="alignment1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="bottom_padding">5</property>
-                        <property name="left_padding">5</property>
-                        <property name="right_padding">5</property>
-                        <child>
-                          <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row_homogeneous">True</property>
-                            <property name="column_homogeneous">True</property>
-                            <child>
-                              <object class="GtkLabel" id="label3">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Potential (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label4">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Time (s)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label5">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label6">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Cleaning</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label7">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Deposition</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="clean_mV">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="dep_mV">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="dep_s">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="clean_s">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">2</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <placeholder/>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                    <child type="label">
-                      <object class="GtkLabel" id="label1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Preconditioning</property>
-                        <property name="use_markup">True</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="padding">2</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkFrame" id="frame2">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">out</property>
-                    <child>
-                      <object class="GtkAlignment" id="alignment2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="bottom_padding">5</property>
-                        <property name="left_padding">5</property>
-                        <property name="right_padding">5</property>
-                        <child>
-                          <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row_homogeneous">True</property>
-                            <property name="column_homogeneous">True</property>
-                            <child>
-                              <object class="GtkEntry" id="scans_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">6</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkCheckButton" id="cyclic_checkbutton">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="receives_default">False</property>
-                                <property name="halign">center</property>
-                                <property name="valign">center</property>
-                                <property name="use_stock">True</property>
-                                <property name="draw_indicator">True</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">5</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label15">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Scans</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">6</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label14">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="tooltip_text" translatable="yes">Scan both forwards and backwards.</property>
-                                <property name="label" translatable="yes">Cyclic Mode</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">5</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="freq_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">4</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="pulse_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">3</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label12">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Frequency (Hz)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">4</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label11">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Pulse Height (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">3</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="step_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="stop_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="start_entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="width_chars">8</property>
-                                <property name="text" translatable="yes">0</property>
-                                <property name="xalign">1</property>
-                                <property name="primary_icon_activatable">False</property>
-                                <property name="secondary_icon_activatable">False</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label10">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Step Size (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label9">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Stop (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label8">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Start (mV)</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">0</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                    <child type="label">
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Experiment</property>
-                        <property name="use_markup">True</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="padding">2</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label13">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">Note: ADC samples 1/(ADC Frequency) before end of pulse. ADC frequency should be significantly larger than SWV frequency to reduce capacitive current.</property>
-                    <property name="wrap">True</property>
-                    <property name="wrap_mode">word-char</property>
-                    <property name="width_chars">43</property>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">2</property>
-                  </packing>
-                </child>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>
diff --git a/dstat_interface/core/microdrop.py b/dstat_interface/core/microdrop.py
deleted file mode 100644
index 3970bfa..0000000
--- a/dstat_interface/core/microdrop.py
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/usr/bin/env python
-#     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 zmq
-
-#signals
-CONREQ = "0"
-CONREP = "1"
-STARTEXP = "start"
-START_REP = "started"
-EXP_FINISH_REQ = "notify_completion"
-EXPFINISHED = "completed"
-INVAL_CMD = "99"
-
-#States
-RECV = 0
-SEND = 1
-
-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)]))
-    
-    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: [uDrop-listen] Connection state invalid, resetting..."
-            # self.reset()
-            # self.__init__(self.port)
-            return (False, "")
-			
-        try:
-            message = self.soc.recv(flags=zmq.NOBLOCK, copy=True)
-            self.state = SEND
-            return (True, message)
-        except zmq.Again:
-            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: [uDrop-reply] Connection state invalid, resetting..."
-            self.reset()
-            self.__init__(self.port)
-            return False
-        self.state = RECV
-        self.soc.send(data)
-        return True
-        
-    def reset(self):
-        """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/core/params.py b/dstat_interface/core/params.py
deleted file mode 100755
index 63c1ae3..0000000
--- a/dstat_interface/core/params.py
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/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
-
-import yaml
-
-from errors import InputError
-
-logger = logging.getLogger(__name__)
-
-
-def get_params(window):
-    """Fetches and returns dict of all parameters for saving."""
-
-    selection = window.exp_window.expcombobox.get_active_id()
-    parameters = {'experiment_index' : selection}
-
-    try:
-        parameters['version'] = window.version
-    except AttributeError: # Will be thrown if not connected to DStat
-        pass
-
-    try:
-        parameters.update(window.adc_pot.params)
-    except InputError:
-        logger.info("No gain selected.")
-    parameters.update(window.exp_window.get_params(selection))
-    parameters.update(window.analysis_opt_window.params)
-
-    return parameters
-
-
-def save_params(window, path):
-    """Fetches current params and saves to path."""
-    logger.info("Save to: %s", path)
-    params = get_params(window)
-
-    with open(path, 'w') as f:
-        yaml.dump(params, f)
-
-
-def load_params(window, path):
-    """Loads params from a path into UI elements."""
-    
-    try:
-        get_params(window)
-    except InputError:  # Will be thrown because no experiment will be selected
-        pass
-    except KeyError:  # Will be thrown because no experiment will be selected
-        pass
-
-    with open(path, 'r') as f:
-        params = yaml.load(f)
-    set_params(window, params)
-
-
-def set_params(window, params):
-    window.adc_pot.params = params
-    if 'experiment_index' in params:
-        window.exp_window.expcombobox.set_active_id(params['experiment_index'])
-        window.exp_window.set_params(params['experiment_index'], params)
-        
-    window.analysis_opt_window.params = params
-
-    window.params_loaded = True
diff --git a/dstat_interface/core/plugin.py b/dstat_interface/core/plugin.py
deleted file mode 100644
index 59ec93d..0000000
--- a/dstat_interface/core/plugin.py
+++ /dev/null
@@ -1,161 +0,0 @@
-# -*- coding: utf-8 -*-
-import logging
-
-from params import get_params, set_params, load_params, save_params
-from interface.save import save_text, save_plot
-from zmq_plugin.plugin import Plugin as ZmqPlugin
-from zmq_plugin.schema import decode_content_data
-import gtk
-import zmq
-
-logger = logging.getLogger(__name__)
-
-
-def get_hub_uri(default='tcp://localhost:31000', parent=None):
-    message = 'Please enter 0MQ hub URI:'
-    d = gtk.MessageDialog(parent=parent, flags=gtk.DIALOG_MODAL |
-                          gtk.DIALOG_DESTROY_WITH_PARENT,
-                          type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK_CANCEL,
-                          message_format=message)
-    entry = gtk.Entry()
-    entry.set_text(default)
-    d.vbox.pack_end(entry)
-    d.vbox.show_all()
-    entry.connect('activate', lambda _: d.response(gtk.RESPONSE_OK))
-    d.set_default_response(gtk.RESPONSE_OK)
-
-    r = d.run()
-    text = entry.get_text().decode('utf8')
-    d.destroy()
-    if r == gtk.RESPONSE_OK:
-        return text
-    else:
-        return None
-
-
-class DstatPlugin(ZmqPlugin):
-    '''
-    Public 0MQ plugin API.
-    '''
-    def __init__(self, parent, *args, **kwargs):
-        self.parent = parent
-        super(DstatPlugin, self).__init__(*args, **kwargs)
-
-    def check_sockets(self):
-        '''
-        Check for messages on command and subscription sockets and process
-        any messages accordingly.
-        '''
-        try:
-            msg_frames = self.command_socket.recv_multipart(zmq.NOBLOCK)
-        except zmq.Again:
-            pass
-        else:
-            self.on_command_recv(msg_frames)
-
-        try:
-            msg_frames = self.subscribe_socket.recv_multipart(zmq.NOBLOCK)
-            source, target, msg_type, msg_json = msg_frames
-            self.most_recent = msg_json
-        except zmq.Again:
-            pass
-        except:
-            logger.error('Error processing message from subscription '
-                         'socket.', exc_info=True)
-        return True
-
-    def on_execute__load_params(self, request):
-        '''
-        Args
-        ----
-
-            params_path (str) : Path to file for parameters yaml file.
-        '''
-        data = decode_content_data(request)
-        load_params(self.parent, data['params_path'])
-
-    def on_execute__save_params(self, request):
-        '''
-        Args
-        ----
-
-            params_path (str) : Path to file for parameters yaml file.
-        '''
-        data = decode_content_data(request)
-        save_params(self.parent, data['params_path'])
-
-    def on_execute__set_params(self, request):
-        '''
-        Args
-        ----
-
-            (dict) : Parameters dictionary in format returned by `get_params`.
-        '''
-        data = decode_content_data(request)
-        set_params(self.parent, data['params'])
-
-    def on_execute__get_params(self, request):
-        return get_params(self.parent)
-
-    def on_execute__run_active_experiment(self, request):
-        data = decode_content_data(request)
-        self.parent.statusbar.push(self.parent.message_context_id, "µDrop "
-                                   "acquisition requested.")
-        return self.parent.run_active_experiment(
-                                         param_override=data.get('params'),
-                                         metadata=data.get('metadata')
-                                                 )
-
-    def on_execute__set_metadata(self, request=None):
-        '''
-        Args
-        ----
-
-            (dict) : Dictionary of metadata to be used in subsequent
-             experiments. Should include `device_id`, `patient_id`, and
-             `experiment_id`. Leave blank to reset all metadata fields or set
-             individual keys to `None` to reset individual values.
-        '''
-        data = decode_content_data(request)
-        self.parent.metadata = request
-
-    def on_execute__save_text(self, request):
-        '''
-        Args
-        ----
-
-            save_data_path (str) : Path to file to save text data.
-        '''
-        data = decode_content_data(request)
-        save_text(self.parent.current_exp, data['save_data_path'])
-
-    def on_execute__save_plot(self, request):
-        '''
-        Args
-        ----
-
-            save_plot_path (str) : Path to file to save plot.
-        '''
-        data = decode_content_data(request)
-        save_plot(self.parent.current_exp, data['save_plot_path'])
-
-    def on_execute__acquisition_complete(self, request):
-        '''
-        Args
-        ----
-
-        Returns
-        -------
-
-            (datetime.datetime or None) : The completion time of the experiment
-                corresponding to the specified UUID.
-        '''
-        data = decode_content_data(request)
-        self.parent.statusbar.push(self.parent.message_context_id, "µDrop "
-                                   "notified of completed acquisition.")
-        if data['experiment_id'] in self.parent.completed_experiment_ids:
-            return self.parent.completed_experiment_ids[data['experiment_id']]
-        elif data['experiment_id'] == self.parent.active_experiment_id:
-            return None
-        else:
-            raise KeyError('Unknown experiment ID: %s' % data['experiment_id'])
diff --git a/dstat_interface/core/state.py b/dstat_interface/core/state.py
new file mode 100644
index 0000000..87c770e
--- /dev/null
+++ b/dstat_interface/core/state.py
@@ -0,0 +1,10 @@
+from datetime import datetime
+import os
+from pathlib import Path
+
+from appdirs import AppDirs
+
+
+app_dirs = AppDirs("dstat-interface", "Wheeler Lab")
+experiment_name = 'experiment_{}'.format(datetime.now().strftime('%y-%m-%d_%H.%M.%S'))
+experiment_folder_location: Path = Path(app_dirs.user_data_dir) / 'Experiments' / experiment_name
diff --git a/dstat_interface/core/tasks.py b/dstat_interface/core/tasks.py
new file mode 100644
index 0000000..7ba8c97
--- /dev/null
+++ b/dstat_interface/core/tasks.py
@@ -0,0 +1,35 @@
+from abc import ABCMeta, abstractmethod
+
+import trio
+
+from dstat_interface.core.experiments.experiment_container import ExperimentContainer
+
+
+class BaseTasks(object, metaclass=ABCMeta):
+    def __init__(self):
+        self.tasks = []
+
+    async def loop(self):
+        async with trio.open_nursery() as nursery:
+            for task in self.tasks:
+                nursery.start_soon(task, nursery.cancel_scope)
+
+
+class ExperimentBaseTasks(BaseTasks):
+    def __init__(self, exp_con: ExperimentContainer):
+        super().__init__()
+        self.exp_con = exp_con
+        self.tasks += [self.update_progress, self.exp_runner]
+
+    @abstractmethod
+    async def update_progress(self, cancel_scope: trio.CancelScope):
+        pass
+
+    async def exp_runner(self, cancel_scope: trio.CancelScope):
+        while True:
+            if not self.exp_con.handler_instance.experiment_running_data():
+                return
+            if not self.exp_con.handler_instance.experiment_running_proc():
+                self.exp_con.handler_instance.get_all_data()
+                return
+            await trio.sleep(0)
diff --git a/dstat_interface/core/utils/__init__.py b/dstat_interface/core/utils/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/dstat_interface/core/utils/version.py b/dstat_interface/core/utils/version.py
deleted file mode 100644
index ae2f3d2..0000000
--- a/dstat_interface/core/utils/version.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""Calculates the current version number.
-
-If possible, uses output of “git describe” modified to conform to the
-visioning scheme that setuptools uses (see PEP 386).  Releases must be
-labelled with annotated tags (signed tags are annotated) of the following
-format:
-
-   v<num>(.<num>)+ [ {a|b|c|rc} <num> (.<num>)* ]
-
-If “git describe” returns an error (likely because we're in an unpacked copy
-of a release tarball, rather than a git working copy), or returns a tag that
-does not match the above format, version is read from RELEASE-VERSION file.
-
-To use this script, simply import it your setup.py file, and use the results
-of getVersion() as your package version:
-
-    import version
-    setup(
-        version=version.getVersion(),
-        .
-        .
-        .
-    )
-
-This will automatically update the RELEASE-VERSION file.  The RELEASE-VERSION
-file should *not* be checked into git but it *should* be included in sdist
-tarballs (as should version.py file).  To do this, run:
-
-    echo include RELEASE-VERSION version.py >>MANIFEST.in
-    echo RELEASE-VERSION >>.gitignore
-
-With that setup, a new release can be labelled by simply invoking:
-
-    git tag -s v1.0
-"""
-
-__author__ = ('Douglas Creager <dcreager@dcreager.net>',
-              'Michal Nazarewicz <mina86@mina86.com>')
-__license__ = 'This file is placed into the public domain.'
-__maintainer__ = 'Michal Nazarewicz'
-__email__ = 'mina86@mina86.com'
-
-__all__ = ('getVersion')
-
-
-import re
-import subprocess
-import sys
-import os.path
-import inspect
-
-RELEASE_VERSION_FILE = '{}/RELEASE-VERSION'.format(
-    os.path.dirname(os.path.abspath(inspect.stack()[0][1])))
-
-# http://www.python.org/dev/peps/pep-0386/
-_PEP386_SHORT_VERSION_RE = r'\d+(?:\.\d+)+(?:(?:[abc]|rc)\d+(?:\.\d+)*)?'
-_PEP386_VERSION_RE = r'^%s(?:\.post\d+)?(?:\.dev\d+)?$' % (
-    _PEP386_SHORT_VERSION_RE)
-_GIT_DESCRIPTION_RE = r'^v(?P<ver>%s)-(?P<commits>\d+)-g(?P<sha>[\da-f]+)$' % (
-    _PEP386_SHORT_VERSION_RE)
-
-
-def readGitVersion():
-    try:
-        proc = subprocess.Popen(('git', 'describe', '--long',
-                                 '--match', 'v[0-9]*.*'),
-                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-        data, _ = proc.communicate()
-        if proc.returncode:
-            return None
-        ver = data.splitlines()[0].strip()
-        proc = subprocess.Popen(('git', 'rev-parse', '--abbrev-ref', 'HEAD'),
-                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-        branch, _ = proc.communicate()
-        if proc.returncode:
-            return None
-    except:
-        return None
-
-    if not ver:
-        return None
-    m = re.search(_GIT_DESCRIPTION_RE, ver)
-    if not m:
-        sys.stderr.write('version: git description (%s) is invalid, '
-                         'ignoring\n' % ver)
-        return None
-
-    commits = int(m.group('commits'))
-
-    if not commits:
-        version = m.group('ver')
-    else:
-        version = '%s.post%d' % (
-            m.group('ver'), commits)
-
-    if branch.strip() != 'master':
-        version += '.dev%d' % int(m.group('sha'), 16)
-
-    return version
-
-
-def readReleaseVersion():
-    try:
-        fd = open(RELEASE_VERSION_FILE)
-        try:
-            ver = fd.readline().strip()
-        finally:
-            fd.close()
-        if not re.search(_PEP386_VERSION_RE, ver):
-            sys.stderr.write('version: release version (%s) is invalid, '
-                             'will use it anyway\n' % ver)
-        return ver
-    except:
-        return None
-
-
-def writeReleaseVersion(version):
-    fd = open(RELEASE_VERSION_FILE, 'w')
-    fd.write('%s\n' % version)
-    fd.close()
-
-
-def getVersion():
-    release_version = readReleaseVersion()
-    version = readGitVersion() or release_version
-    if not version:
-        raise ValueError('Cannot find the version number')
-    if version != release_version:
-        writeReleaseVersion(version)
-    return version
-
-
-if __name__ == '__main__':
-    print getVersion()
-
diff --git a/dstat_interface/main.py b/dstat_interface/main.py
deleted file mode 100755
index 32caa5d..0000000
--- a/dstat_interface/main.py
+++ /dev/null
@@ -1,854 +0,0 @@
-#!/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/>.
-
-""" GUI Interface for Wheeler Lab DStat """
-
-from __future__ import division, absolute_import, print_function, unicode_literals
-
-import sys
-import os
-import platform
-import multiprocessing
-import uuid
-from collections import OrderedDict
-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 gi
-    gi.require_version('Gtk', '3.0')
-    from gi.repository import Gtk, GObject
-except ImportError:
-    print("ERR: GTK not available")
-    sys.exit(1)
-
-mod_dir = os.path.dirname(os.path.abspath(__file__))
-conf_path = os.path.join(os.path.expanduser("~"), '.dstat-interface')
-
-# if __name__ == "__parents_main__":  # Only runs for forking emulation on win
-# sys.path.append(mod_dir)
-
-# Setup Logging
-logger = logging.getLogger(__name__)
-core_logger = logging.getLogger("dstat_interface.core")
-loggers = [logger, core_logger]
-
-log_handler = logging.StreamHandler()
-log_formatter = logging.Formatter(
-                    fmt='%(asctime)s %(levelname)s: [%(name)s] %(message)s',
-                    datefmt='%H:%M:%S'
-                )
-log_handler.setFormatter(log_formatter)
-
-for log in loggers:
-    log.setLevel(level=logging.INFO)
-    log.addHandler(log_handler)
-
-
-class Main(GObject.Object):
-    """Main program """
-    __gsignals__ = {
-        b'exp_start': (GObject.SIGNAL_RUN_FIRST, None, ()),
-        b'exp_stop': (GObject.SIGNAL_RUN_FIRST, None, ())
-    }
-
-    def __init__(self):
-        super(Main, self).__init__()
-        self.builder = Gtk.Builder()
-        self.builder.add_from_file(
-            os.path.join(mod_dir, 'core/interface/dstatinterface.glade'))
-        self.builder.connect_signals(self)
-        self.cell = Gtk.CellRendererText()
-
-        # Create instance of interface components
-        self.statusbar = self.builder.get_object('statusbar')
-        self.ocp_disp = self.builder.get_object('ocp_disp')
-        self.window = self.builder.get_object('window1')
-        self.aboutdialog = self.builder.get_object('aboutdialog1')
-        self.stopbutton = self.builder.get_object('pot_stop')
-        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.error_context_id = self.statusbar.get_context_id("error")
-        self.message_context_id = self.statusbar.get_context_id("message")
-
-        self.exp_window = exp_window.Experiments(self.builder)
-        self.exp_window.connect('run_utility', self.stop_ocp)
-        self.exp_window.connect('done_utility', self.start_ocp)
-        
-        self.analysis_opt_window = analysis.AnalysisOptions(self.builder)
-
-        # Setup Autosave
-        self.autosave_checkbox = self.builder.get_object('autosave_checkbutton')
-        self.autosavedir_button = self.builder.get_object('autosavedir_button')
-        self.autosavename = self.builder.get_object('autosavename')
-
-        # Setup Plots
-        self.plot_notebook = self.builder.get_object('plot_notebook')
-        self.main_notebook = self.builder.get_object('main_notebook')
-        self.data_view = data_view.DataPage(self.main_notebook)
-        self.info_page = data_view.InfoPage(self.main_notebook)
-
-        # 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')
-        self.adc_pot_container.reparent(self.adc_pot_box)
-
-        # fill serial
-        self.serial_connect = self.builder.get_object('serial_connect')
-        self.serial_pmt_connect = self.builder.get_object('pmt_mode')
-        self.serial_disconnect = self.builder.get_object('serial_disconnect')
-        self.serial_disconnect.set_sensitive(False)
-        self.serial_combobox = self.builder.get_object('serial_combobox')
-        self.serial_combobox.pack_start(self.cell, True)
-        self.serial_combobox.add_attribute(self.cell, 'text', 0)
-        self.serial_liststore = self.builder.get_object('serial_liststore')
-        self.serial_devices = dstat.comm.SerialDevices()
-
-        for i in self.serial_devices.ports:
-            self.serial_liststore.append([i])
-
-        self.serial_combobox.set_active(0)
-
-        self.spinner = self.builder.get_object('spinner')
-
-        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
-        try:
-            ver = getVersion()
-        except ValueError:
-            ver = "1.x"
-            logger.warning("Could not fetch version number")
-        self.mainwindow.set_title(" ".join(("DStat Interface", ver)))
-        self.aboutdialog.set_version(ver)
-
-        self.mainwindow.show_all()
-        self.exp_window.hide_exps()
-        
-        self.expnumber = 0
-
-        self.connected = False
-        self.pmt_mode = False
-        
-        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
-        
-        self.metadata = None # Should only be added to by plugin interface
-
-        self.params_loaded = False
-        # Disable 0MQ plugin API by default.
-        self.plugin = None
-        self.plugin_timeout_id = None
-        # UUID for active experiment.
-        self.active_experiment_id = None
-        # UUIDs for completed experiments.
-        self.completed_experiment_ids = OrderedDict()
-
-    def on_window1_destroy(self, object, data=None):
-        """ Quit when main window closed."""
-        self.quit()
-
-    def on_gtk_quit_activate(self, menuitem, data=None):
-        """Quit when Quit selected from menu."""
-        self.quit()
-
-    def quit(self):
-        """Disconnect and save parameters on quit."""
-        
-        try:
-            params.save_params(self, os.path.join(conf_path, 'last_params.yml'))
-            self.on_serial_disconnect_clicked()
-        except KeyError:
-            pass
-            
-        mainloop.quit()
-
-    def on_gtk_about_activate(self, menuitem, data=None):
-        """Display the about window."""
-        self.aboutdialog.run()  # waits for user to click close
-        self.aboutdialog.hide()
-
-    def on_menu_analysis_options_activate(self, menuitem, data=None):
-        self.analysis_opt_window.show()
-
-    def on_serial_refresh_clicked(self, data=None):
-        """Refresh list of serial devices."""
-        try:
-            self.serial_devices.refresh()
-            self.serial_liststore.clear()
-        except ValueError:
-            logger.warning("No DStats found")
-
-        for i in self.serial_devices.ports:
-            self.serial_liststore.append([i])
-
-    def on_serial_connect_clicked(self, widget):
-        """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:
-            self.serial_connect.set_sensitive(False)
-            self.serial_pmt_connect.set_sensitive(False)
-            dstat.comm.version_check(
-                self.serial_liststore.get_value(selection, 0)
-            )
-
-            dstat.state.board_instance = boards.find_board(
-                dstat.state.dstat_version)()
-
-            self.statusbar.remove_all(self.error_context_id)
-
-            self.adc_pot.set_version()
-            self.statusbar.push(
-                self.message_context_id,
-                "DStat version: {}".format(
-                    dstat.state.dstat_version.base_version)
-            )
-
-            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.connected = True
-            self.serial_disconnect.set_sensitive(True)
-
-        except:
-            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:
-            try:
-                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:
-                logger.info("No previous parameters found.")
-        
-    def on_serial_disconnect_clicked(self, data=None):
-        """Disconnect from DStat."""
-        if self.connected == False:
-            return
-
-        try:
-            if self.ocp_is_running:
-                self.stop_ocp()
-            else:
-                self.on_pot_stop_clicked()
-            dstat.state.ser.disconnect()
-
-        except AttributeError as err:
-            logger.warning("AttributeError: %s", err)
-            pass
-        
-        if self.pmt_mode is True:
-            self.adc_pot.ui['short_true'].set_sensitive(True)
-        
-        self.pmt_mode = False
-        self.connected = False
-        self.serial_connect.set_sensitive(True)
-        self.serial_pmt_connect.set_sensitive(True)
-        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)
-        
-        dstat.state.reset()
-
-    def start_ocp(self, data=None):
-        """Start OCP measurements."""
-        if dstat.state.dstat_version >= parse_version('1.2'):
-            # Flush data pipe
-            dstat.state.ser.flush_data()
-
-            if self.pmt_mode is True:
-                logger.info("Start PMT idle mode")
-                dstat.state.ser.start_exp(idle.PMTIdle())
-                self.ocp_is_running = True
-                self.ocp_proc = (GObject.timeout_add(250, self.ocp_running_proc)
-                                 ,
-                                 )
-
-            else:
-                logger.info("Start OCP")
-                dstat.state.ser.start_exp(idle.OCPExp())
-
-                self.ocp_proc = (GObject.timeout_add(300, self.ocp_running_data),
-                                 GObject.timeout_add(250, self.ocp_running_proc)
-                                )
-                self.ocp_is_running = False
-                
-            GObject.timeout_add(100, self.ocp_assert)  # Check if getting data
-
-        else:
-            logger.info("OCP measurements not supported on v1.1 boards.")
-        return
-
-    def stop_ocp(self, data=None):
-        """Stop OCP measurements."""
-
-        if dstat.state.dstat_version >= parse_version('1.2'):
-            if self.pmt_mode == True:
-                logger.info("Stop PMT idle mode")
-            else:
-                logger.info("Stop OCP")
-            dstat.state.ser.send_ctrl('a')
-            
-            for i in self.ocp_proc:
-                GObject.source_remove(i)
-            while self.ocp_running_proc():
-                pass
-            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:
-            logger.error("OCP measurements not supported on v1.1 boards.")
-        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):
-        """Receive OCP value from experiment process and update ocp_disp field
-
-        Returns:
-        True -- when experiment is continuing to keep function in GTK's queue.
-        False -- when experiment process signals EOFError or IOError to remove
-            function from GTK's queue.
-        """
-
-        try:
-            incoming = dstat.state.ser.get_data()
-            while incoming is not None:
-                if isinstance(incoming, basestring): # test if incoming is str
-                    self.on_serial_disconnect_clicked()
-                    return False
-                
-                try:
-                    data = "".join(["OCP: ",
-                                    "{0:.3f}".format(incoming),
-                                    " V"])
-                    self.ocp_disp.set_text(data)
-                    self.ocp_is_running = True
-                except ValueError:
-                    pass
-
-                incoming = dstat.state.ser.get_data()
-
-            return True
-
-        except EOFError:
-            return False
-        except IOError:
-            return False
-
-    def ocp_running_proc(self):
-        """Handles signals on proc_pipe_p for OCP.
-
-        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:
-            proc_buffer = dstat.state.ser.get_proc()
-            while proc_buffer is not None:
-                logger.debug("ocp_running_proc: %s", proc_buffer)
-                if proc_buffer in ["DONE", "SERIAL_ERROR", "ABORT"]:
-                    if proc_buffer == "SERIAL_ERROR":
-                        self.on_serial_disconnect_clicked()
-
-                    dstat.state.ser.flush_data()
-                    return False
-                proc_buffer = dstat.state.ser.get_proc()
-            return True
-
-        except EOFError:
-            return False
-        except IOError:
-            return False
-
-    def on_pot_start_clicked(self, data=None):
-        try:
-            self.run_active_experiment()
-        except (ValueError, KeyError, InputError, SerialException,
-                AssertionError):
-            # Ignore expected exceptions when triggering experiment from UI.
-            pass
-
-    def run_active_experiment(self, param_override=None, metadata=None):
-        """Run currently visible experiment."""
-        # Assign current experiment a unique identifier.
-        experiment_id = uuid.uuid4()
-        self.active_experiment_id = experiment_id
-        logger.info("Current measurement id: %s", experiment_id.hex)
-        
-        self.metadata = metadata
-        
-        if self.metadata is not None:
-            logger.info("Loading external metadata")       
-
-        def exceptions():
-            """ Cleans up after errors """
-            self.spinner.stop()
-            self.startbutton.set_sensitive(True)
-            self.stopbutton.set_sensitive(False)
-            self.start_ocp()
-
-        self.stop_ocp()
-        self.statusbar.remove_all(self.error_context_id)
-
-        dstat.state.ser.flush_data()
-
-        parameters = {}
-        parameters['metadata'] = self.metadata
-
-        # Make sure these are defined
-        parameters['sync_true'] = False
-        parameters['shutter_true'] = False
-        try:
-            if param_override is not None:
-                params.set_params(self, param_override)
-            
-            parameters.update(params.get_params(self))
-            
-            self.line = 0
-            self.lastline = 0
-            self.lastdataline = 0
-
-            self.spinner.start()
-            self.startbutton.set_sensitive(False)
-            self.stopbutton.set_sensitive(True)
-            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)
-            
-            plot_ui.replace_notebook_exp(
-                self.plot_notebook, self.current_exp, self.window
-                )
-            
-            self.data_view.clear_exps()
-            self.info_page.clear()
-            
-            dstat.state.ser.start_exp(self.current_exp)
-
-            # Flush data pipe
-            dstat.state.ser.flush_data()
-            self.current_exp.setup_loops(callbacks)
-            return experiment_id
-
-        except ValueError as i:
-            logger.info("ValueError: %s",i)
-            self.statusbar.push(self.error_context_id,
-                                "Experiment parameters must be integers.")
-            exceptions()
-            raise
-
-        except KeyError as i:
-            import traceback
-            traceback.print_exc()
-            logger.info("KeyError: %s", i)
-            self.statusbar.push(self.error_context_id,
-                                "Experiment parameters must be integers.")
-            exceptions()
-            raise
-
-        except InputError as err:
-            logger.info("InputError: %s", err)
-            self.statusbar.push(self.error_context_id, err.msg)
-            exceptions()
-            raise
-
-        except SerialException as err:
-            logger.info("SerialException: %s", err)
-            self.statusbar.push(self.error_context_id,
-                                "Could not establish serial connection.")
-            exceptions()
-            raise
-
-        except AssertionError as err:
-            logger.info("AssertionError: %s", err)
-            self.statusbar.push(self.error_context_id, str(err))
-            exceptions()
-            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):
-        """Receive data from experiment process and add to
-        current_exp.data['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:
-            incoming = dstat.state.ser.get_data()
-            while incoming is not None:
-                try:
-                    self.line, data = incoming
-                    if self.line > self.lastdataline:
-                        newline = True
-                        try:
-                            logger.info("running scan_process()")
-                            self.current_exp.scan_process(self.lastdataline)
-                        except AttributeError:
-                            pass
-                        self.lastdataline = self.line
-                    else:
-                        newline = False
-                    self.current_exp.store_data(incoming, newline)
-                    
-                    if newline:
-                        self.experiment_running_plot()
-                except TypeError:
-                    pass
-                incoming = dstat.state.ser.get_data()
-            return True
-
-        except EOFError as err:
-            logger.error(err)
-            self.experiment_done()
-            return False
-        except IOError as err:
-            logger.error(err)
-            self.experiment_done()
-            return False
-
-    def experiment_running_proc(self):
-        """Receive proc signals from experiment process.
-        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:
-            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 in ["DONE", "SERIAL_ERROR", "ABORT"]:
-                    self.experiment_done()
-                    if proc_buffer == "SERIAL_ERROR":
-                        self.on_serial_disconnect_clicked()
-
-                else:
-                    logger.warning("Unrecognized experiment return code: %s",
-                                   proc_buffer)
-                return False
-            return True
-
-        except EOFError as err:
-            logger.warning("EOFError: %s", err)
-            self.experiment_done()
-            return False
-        except IOError as err:
-            logger.warning("IOError: %s", err)
-            self.experiment_done()
-            return False
-
-    def experiment_running_plot(self, force_refresh=False):
-        """Plot all data in current_exp.data.
-        Run in GTK main loop. Always returns True so must be manually
-        removed from GTK's queue.
-        """
-        for plot in self.current_exp.plots:
-            if (plot.scan_refresh and self.line > self.lastline):
-                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.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)
-            
-            if plot.continuous_refresh is True or force_refresh is True:
-                plot.redraw()
-        return True
-
-    def experiment_done(self, widget=None):
-        """Clean up after data acquisition is complete. Update plot and
-        copy data to raw data tab. Saves data if autosave enabled.
-        """
-        try:
-            # Run Analysis
-            analysis.do_analysis(self.current_exp)
-            
-            # Write DStat commands
-            self.info_page.set_text(self.current_exp.get_info_text())
-            
-            try:
-                self.statusbar.push(
-                    self.message_context_id,
-                    "Integral: %s A" % self.current_exp.analysis['FT Integral'][0][1]
-                )
-            except KeyError:
-                pass
-
-            # Data Output
-            analysis_buffer = []
-
-            if self.current_exp.analysis != {}:
-                analysis_buffer.append("# ANALYSIS")
-                for key, value in self.current_exp.analysis.iteritems():
-                    analysis_buffer.append("#  %s:" % key)
-                    for scan in value:
-                        number, result = scan
-                        analysis_buffer.append(
-                            "#    Scan %s -- %s" % (number, result)
-                            )
-
-            for i in analysis_buffer:
-                self.info_page.add_line(i)
-
-            self.data_view.add_exp(self.current_exp)
-
-            # Autosaving
-            if self.autosave_checkbox.get_active():
-                save.autoSave(self.current_exp,
-                              self.autosavedir_button.get_filename().decode('utf-8'),
-                              self.autosavename.get_text()
-                              )
-
-                save.autoPlot(self.current_exp,
-                              self.autosavedir_button.get_filename().decode('utf-8'),
-                              self.autosavename.get_text()
-                              )
-        
-        # uDrop
-        # UI stuff
-        finally:
-            self.metadata = None # Reset metadata
-            
-            self.spinner.stop()
-            self.exp_progressbar.set_fraction(0)
-            self.stopbutton.set_sensitive(False)
-
-            self.start_ocp()
-            self.completed_experiment_ids[self.active_experiment_id] =\
-                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):
-        """Stop current experiment. Signals experiment process to stop."""
-        try:
-            dstat.state.ser.stop_exp()
-
-        except AttributeError:
-            pass
-        except:
-            logger.warning(sys.exc_info())
-
-    def on_file_save_exp_activate(self, menuitem, data=None):
-        """Activate dialogue to save current experiment data. """
-        try:
-            save.manSave(self.current_exp)
-        except AttributeError:
-            logger.warning("Tried to save with no experiment run")
-
-    def on_file_save_plot_activate(self, menuitem, data=None):
-        """Activate dialogue to save current plot."""
-        try:
-            save.plot_save_dialog(self.current_exp)
-        except AttributeError:
-            logger.warning("Tried to save with no experiment run")
-
-    def on_file_save_params_activate(self, menuitem, data=None):
-        """Activate dialogue to save current experiment parameters. """
-        save.man_param_save(self)
-
-    def on_file_load_params_activate(self, menuitem, data=None):
-        """Activate dialogue to load experiment parameters from file. """
-        save.man_param_load(self)
-
-    def on_menu_dropbot_connect_activate(self, menuitem, data=None):
-        """Listen for remote control connection from µDrop."""
-
-        # Prompt user for 0MQ plugin hub URI.
-        zmq_plugin_hub_uri = get_hub_uri(parent=self.window)
-
-        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.enable_plugin(zmq_plugin_hub_uri)
-
-    def on_menu_dropbot_disconnect_activate(self, menuitem=None, data=None):
-        """Disconnect µDrop connection and stop listening."""
-        self.cleanup_plugin()
-        self.dropbot_enabled = False
-        self.menu_dropbot_connect.set_sensitive(True)
-        self.menu_dropbot_disconnect.set_sensitive(False)
-        self.statusbar.push(self.message_context_id, "µDrop disconnected.")
-
-    def enable_plugin(self, hub_uri):
-        '''
-        Connect to 0MQ plugin hub to expose public D-Stat API.
-
-        Args
-        ----
-
-            hub_uri (str) : URI for 0MQ plugin hub.
-        '''
-        self.cleanup_plugin()
-        # Initialize 0MQ hub plugin and subscribe to hub messages.
-        self.plugin = DstatPlugin(self, 'dstat-interface', hub_uri,
-                                  subscribe_options={zmq.SUBSCRIBE: ''})
-        # Initialize sockets.
-        self.plugin.reset()
-
-        # Periodically process outstanding message received on plugin sockets.
-        self.plugin_timeout_id = Gtk.timeout_add(500,
-                                                 self.plugin.check_sockets)
-
-    def cleanup_plugin(self):
-        if self.plugin_timeout_id is not None:
-            GObject.source_remove(self.plugin_timeout_id)
-        if self.plugin is not None:
-            self.plugin = None
-
-
-if __name__ == "__main__":
-    multiprocessing.freeze_support()
-    GObject.threads_init()
-    MAIN = Main()
-    mainloop = GObject.MainLoop()
-    try:
-        mainloop.run()
-    except KeyboardInterrupt:
-        logger.info('Ctrl+C hit, quitting')
-        MAIN.quit()
diff --git a/dstat_interface/utils.py b/dstat_interface/utils.py
new file mode 100644
index 0000000..cad25a9
--- /dev/null
+++ b/dstat_interface/utils.py
@@ -0,0 +1,25 @@
+import os
+import sys
+from pathlib import Path
+
+
+class Singleton(type):
+    _instances = {}
+
+    def __call__(cls, *args, **kwargs):
+        if cls not in cls._instances:
+            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
+        return cls._instances[cls]
+
+
+def runtime_dir():
+    """Get the runtime directory where we can create named pipes"""
+    if sys.platform.startswith('linux'):
+        return os.environ['XDG_RUNTIME_DIR']
+    elif sys.platform == 'darwin':
+        return '/private/var/tmp'
+    elif sys.platform == 'win32':
+        # Windows uses named pipes which aren't part of the normal filesystem
+        return ''
+    else:
+        raise NotImplementedError('Where do I create named pipes on this platform?')
diff --git a/pavement.py b/pavement.py
deleted file mode 100644
index fc19855..0000000
--- a/pavement.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import sys
-
-from paver.easy import task, needs, path, sh, cmdopts, options
-from paver.setuputils import setup, install_distutils_tasks, find_packages
-from distutils.extension import Extension
-from distutils.dep_util import newer
-
-sys.path.insert(0, path('.').abspath())
-import version
-
-setup(name='dstat_interface',
-      version=version.getVersion(),
-      description='Interface software for DStat potentiostat.',
-      keywords='',
-      author='Michael D. M Dryden',
-      author_email='mdryden@chem.utoronto.ca',
-      url='http://microfluidics.utoronto.ca/dstat',
-      license='GPLv3',
-      packages=find_packages(),
-      install_requires=['matplotlib', 'numpy', 'pyserial', 'pyzmq',
-                        'pyyaml','seaborn', 'setuptools', 
-                        'zmq-plugin>=0.2.post2'],
-      # Install data listed in `MANIFEST.in`
-      include_package_data=True)
-
-
-@task
-@needs('generate_setup', 'minilib', 'setuptools.command.sdist')
-def sdist():
-    """Overrides sdist to make sure that our setup.py is generated."""
-    pass
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..88e5fef
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,199 @@
+[[package]]
+name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "numpy"
+version = "1.19.4"
+description = "NumPy is the fundamental package for array computing with Python."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "pyserial"
+version = "3.4"
+description = "Python Serial Port Extension"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pyside2"
+version = "5.15.1"
+description = "Python bindings for the Qt cross-platform application and UI framework"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <3.10"
+
+[package.dependencies]
+shiboken2 = "5.15.1"
+
+[[package]]
+name = "pyyaml"
+version = "5.3.1"
+description = "YAML parser and emitter for Python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "pyzmq"
+version = "19.0.2"
+description = "Python bindings for 0MQ"
+category = "main"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
+
+[[package]]
+name = "shiboken2"
+version = "5.15.1"
+description = "Python / C++ bindings helper module"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <3.10"
+
+[[package]]
+name = "single-source"
+version = "0.2.0"
+description = "Access to the project version in Python code for PEP 621-style projects"
+category = "main"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[[package]]
+name = "tqdm"
+version = "4.51.0"
+description = "Fast, Extensible Progress Meter"
+category = "main"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*"
+
+[package.extras]
+dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"]
+
+[metadata]
+lock-version = "1.1"
+python-versions = ">=3.8,<3.10"
+content-hash = "0f3e920ba999f9294c04581da00d38a389fbb7f0cf3e693c1138bb64d06ccf06"
+
+[metadata.files]
+appdirs = [
+    {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+    {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
+numpy = [
+    {file = "numpy-1.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9b30d4bd69498fc0c3fe9db5f62fffbb06b8eb9321f92cc970f2969be5e3949"},
+    {file = "numpy-1.19.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fedbd128668ead37f33917820b704784aff695e0019309ad446a6d0b065b57e4"},
+    {file = "numpy-1.19.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8ece138c3a16db8c1ad38f52eb32be6086cc72f403150a79336eb2045723a1ad"},
+    {file = "numpy-1.19.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:64324f64f90a9e4ef732be0928be853eee378fd6a01be21a0a8469c4f2682c83"},
+    {file = "numpy-1.19.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:ad6f2ff5b1989a4899bf89800a671d71b1612e5ff40866d1f4d8bcf48d4e5764"},
+    {file = "numpy-1.19.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d6c7bb82883680e168b55b49c70af29b84b84abb161cbac2800e8fcb6f2109b6"},
+    {file = "numpy-1.19.4-cp36-cp36m-win32.whl", hash = "sha256:13d166f77d6dc02c0a73c1101dd87fdf01339febec1030bd810dcd53fff3b0f1"},
+    {file = "numpy-1.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:448ebb1b3bf64c0267d6b09a7cba26b5ae61b6d2dbabff7c91b660c7eccf2bdb"},
+    {file = "numpy-1.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:27d3f3b9e3406579a8af3a9f262f5339005dd25e0ecf3cf1559ff8a49ed5cbf2"},
+    {file = "numpy-1.19.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:16c1b388cc31a9baa06d91a19366fb99ddbe1c7b205293ed072211ee5bac1ed2"},
+    {file = "numpy-1.19.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e5b6ed0f0b42317050c88022349d994fe72bfe35f5908617512cd8c8ef9da2a9"},
+    {file = "numpy-1.19.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:18bed2bcb39e3f758296584337966e68d2d5ba6aab7e038688ad53c8f889f757"},
+    {file = "numpy-1.19.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:fe45becb4c2f72a0907c1d0246ea6449fe7a9e2293bb0e11c4e9a32bb0930a15"},
+    {file = "numpy-1.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:6d7593a705d662be5bfe24111af14763016765f43cb6923ed86223f965f52387"},
+    {file = "numpy-1.19.4-cp37-cp37m-win32.whl", hash = "sha256:6ae6c680f3ebf1cf7ad1d7748868b39d9f900836df774c453c11c5440bc15b36"},
+    {file = "numpy-1.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9eeb7d1d04b117ac0d38719915ae169aa6b61fca227b0b7d198d43728f0c879c"},
+    {file = "numpy-1.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cb1017eec5257e9ac6209ac172058c430e834d5d2bc21961dceeb79d111e5909"},
+    {file = "numpy-1.19.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:edb01671b3caae1ca00881686003d16c2209e07b7ef8b7639f1867852b948f7c"},
+    {file = "numpy-1.19.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f29454410db6ef8126c83bd3c968d143304633d45dc57b51252afbd79d700893"},
+    {file = "numpy-1.19.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:ec149b90019852266fec2341ce1db513b843e496d5a8e8cdb5ced1923a92faab"},
+    {file = "numpy-1.19.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1aeef46a13e51931c0b1cf8ae1168b4a55ecd282e6688fdb0a948cc5a1d5afb9"},
+    {file = "numpy-1.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:08308c38e44cc926bdfce99498b21eec1f848d24c302519e64203a8da99a97db"},
+    {file = "numpy-1.19.4-cp38-cp38-win32.whl", hash = "sha256:5734bdc0342aba9dfc6f04920988140fb41234db42381cf7ccba64169f9fe7ac"},
+    {file = "numpy-1.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:09c12096d843b90eafd01ea1b3307e78ddd47a55855ad402b157b6c4862197ce"},
+    {file = "numpy-1.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e452dc66e08a4ce642a961f134814258a082832c78c90351b75c41ad16f79f63"},
+    {file = "numpy-1.19.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a5d897c14513590a85774180be713f692df6fa8ecf6483e561a6d47309566f37"},
+    {file = "numpy-1.19.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:a09f98011236a419ee3f49cedc9ef27d7a1651df07810ae430a6b06576e0b414"},
+    {file = "numpy-1.19.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:50e86c076611212ca62e5a59f518edafe0c0730f7d9195fec718da1a5c2bb1fc"},
+    {file = "numpy-1.19.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f0d3929fe88ee1c155129ecd82f981b8856c5d97bcb0d5f23e9b4242e79d1de3"},
+    {file = "numpy-1.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c42c4b73121caf0ed6cd795512c9c09c52a7287b04d105d112068c1736d7c753"},
+    {file = "numpy-1.19.4-cp39-cp39-win32.whl", hash = "sha256:8cac8790a6b1ddf88640a9267ee67b1aee7a57dfa2d2dd33999d080bc8ee3a0f"},
+    {file = "numpy-1.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:4377e10b874e653fe96985c05feed2225c912e328c8a26541f7fc600fb9c637b"},
+    {file = "numpy-1.19.4-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:2a2740aa9733d2e5b2dfb33639d98a64c3b0f24765fed86b0fd2aec07f6a0a08"},
+    {file = "numpy-1.19.4.zip", hash = "sha256:141ec3a3300ab89c7f2b0775289954d193cc8edb621ea05f99db9cb181530512"},
+]
+pyserial = [
+    {file = "pyserial-3.4-py2.py3-none-any.whl", hash = "sha256:e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"},
+    {file = "pyserial-3.4.tar.gz", hash = "sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627"},
+]
+pyside2 = [
+    {file = "PySide2-5.15.1-5.15.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:9d7c076d1c4665c57a7e0b912a52be132410d369c98325c0583c323f10cbe3aa"},
+    {file = "PySide2-5.15.1-5.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:66cc78e4c6b0754409c79a3c0321a87728d1ef2cc137c61737f1cc1b24574988"},
+    {file = "PySide2-5.15.1-5.15.1-cp35.cp36.cp37.cp38.cp39-abi3-macosx_10_13_intel.whl", hash = "sha256:8b737c2f42770918623e704d1bef9cd3df39bd09f1bc61cc550bdc6daa7088f6"},
+    {file = "PySide2-5.15.1-5.15.1-cp35.cp36.cp37.cp38.cp39-abi3-manylinux1_x86_64.whl", hash = "sha256:76f0e0875ed9eb526a98b9e8dedac033a25cdcf7a51eb366451a0202b140d8d4"},
+    {file = "PySide2-5.15.1-5.15.1-cp35.cp36.cp37.cp38.cp39-none-win32.whl", hash = "sha256:db7ae55f88a13fda43ed956a12853f8b5e6ae7148f9ac3565719afa03bbeca8e"},
+    {file = "PySide2-5.15.1-5.15.1-cp35.cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:3c9a24557edf25b60fded6ed99a9b5e4b287be1cc3857e76e18dd930e54240e3"},
+]
+pyyaml = [
+    {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"},
+    {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"},
+    {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"},
+    {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"},
+    {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"},
+    {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"},
+    {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"},
+    {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"},
+    {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"},
+    {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"},
+    {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"},
+]
+pyzmq = [
+    {file = "pyzmq-19.0.2-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:59f1e54627483dcf61c663941d94c4af9bf4163aec334171686cdaee67974fe5"},
+    {file = "pyzmq-19.0.2-cp27-cp27m-win32.whl", hash = "sha256:c36ffe1e5aa35a1af6a96640d723d0d211c5f48841735c2aa8d034204e87eb87"},
+    {file = "pyzmq-19.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:0a422fc290d03958899743db091f8154958410fc76ce7ee0ceb66150f72c2c97"},
+    {file = "pyzmq-19.0.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c20dd60b9428f532bc59f2ef6d3b1029a28fc790d408af82f871a7db03e722ff"},
+    {file = "pyzmq-19.0.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d46fb17f5693244de83e434648b3dbb4f4b0fec88415d6cbab1c1452b6f2ae17"},
+    {file = "pyzmq-19.0.2-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:f1a25a61495b6f7bb986accc5b597a3541d9bd3ef0016f50be16dbb32025b302"},
+    {file = "pyzmq-19.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:ab0d01148d13854de716786ca73701012e07dff4dfbbd68c4e06d8888743526e"},
+    {file = "pyzmq-19.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:720d2b6083498a9281eaee3f2927486e9fe02cd16d13a844f2e95217f243efea"},
+    {file = "pyzmq-19.0.2-cp35-cp35m-win32.whl", hash = "sha256:29d51279060d0a70f551663bc592418bcad7f4be4eea7b324f6dd81de05cb4c1"},
+    {file = "pyzmq-19.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:5120c64646e75f6db20cc16b9a94203926ead5d633de9feba4f137004241221d"},
+    {file = "pyzmq-19.0.2-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:8a6ada5a3f719bf46a04ba38595073df8d6b067316c011180102ba2a1925f5b5"},
+    {file = "pyzmq-19.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fa411b1d8f371d3a49d31b0789eb6da2537dadbb2aef74a43aa99a78195c3f76"},
+    {file = "pyzmq-19.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:00dca814469436455399660247d74045172955459c0bd49b54a540ce4d652185"},
+    {file = "pyzmq-19.0.2-cp36-cp36m-win32.whl", hash = "sha256:046b92e860914e39612e84fa760fc3f16054d268c11e0e25dcb011fb1bc6a075"},
+    {file = "pyzmq-19.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99cc0e339a731c6a34109e5c4072aaa06d8e32c0b93dc2c2d90345dd45fa196c"},
+    {file = "pyzmq-19.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e36f12f503511d72d9bdfae11cadbadca22ff632ff67c1b5459f69756a029c19"},
+    {file = "pyzmq-19.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c40fbb2b9933369e994b837ee72193d6a4c35dfb9a7c573257ef7ff28961272c"},
+    {file = "pyzmq-19.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5d9fc809aa8d636e757e4ced2302569d6e60e9b9c26114a83f0d9d6519c40493"},
+    {file = "pyzmq-19.0.2-cp37-cp37m-win32.whl", hash = "sha256:3fa6debf4bf9412e59353defad1f8035a1e68b66095a94ead8f7a61ae90b2675"},
+    {file = "pyzmq-19.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:73483a2caaa0264ac717af33d6fb3f143d8379e60a422730ee8d010526ce1913"},
+    {file = "pyzmq-19.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:36ab114021c0cab1a423fe6689355e8f813979f2c750968833b318c1fa10a0fd"},
+    {file = "pyzmq-19.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8b66b94fe6243d2d1d89bca336b2424399aac57932858b9a30309803ffc28112"},
+    {file = "pyzmq-19.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:654d3e06a4edc566b416c10293064732516cf8871a4522e0a2ba00cc2a2e600c"},
+    {file = "pyzmq-19.0.2-cp38-cp38-win32.whl", hash = "sha256:276ad604bffd70992a386a84bea34883e696a6b22e7378053e5d3227321d9702"},
+    {file = "pyzmq-19.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:09d24a80ccb8cbda1af6ed8eb26b005b6743e58e9290566d2a6841f4e31fa8e0"},
+    {file = "pyzmq-19.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:18189fc59ff5bf46b7ccf5a65c1963326dbfc85a2bc73e9f4a90a40322b992c8"},
+    {file = "pyzmq-19.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b1dd4cf4c5e09cbeef0aee83f3b8af1e9986c086a8927b261c042655607571e8"},
+    {file = "pyzmq-19.0.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c6d653bab76b3925c65d4ac2ddbdffe09710f3f41cc7f177299e8c4498adb04a"},
+    {file = "pyzmq-19.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:949a219493a861c263b75a16588eadeeeab08f372e25ff4a15a00f73dfe341f4"},
+    {file = "pyzmq-19.0.2-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:c1a31cd42905b405530e92bdb70a8a56f048c8a371728b8acf9d746ecd4482c0"},
+    {file = "pyzmq-19.0.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a7e7f930039ee0c4c26e4dfee015f20bd6919cd8b97c9cd7afbde2923a5167b6"},
+    {file = "pyzmq-19.0.2.tar.gz", hash = "sha256:296540a065c8c21b26d63e3cea2d1d57902373b16e4256afe46422691903a438"},
+]
+shiboken2 = [
+    {file = "shiboken2-5.15.1-5.15.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:610e3fd0cd16e85f85ef81bc09762868ade2239d43f6de6f3db0733ee6810619"},
+    {file = "shiboken2-5.15.1-5.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:28f98204d0ad77861b553db12eec0fa04c6e2da7e16126e761bdd70bbd8ae511"},
+    {file = "shiboken2-5.15.1-5.15.1-cp35.cp36.cp37.cp38.cp39-abi3-macosx_10_13_intel.whl", hash = "sha256:44d0cecf0b0b5d844d7cb1edccc209cb9cd26f2c1624298af9905bb41782fbbf"},
+    {file = "shiboken2-5.15.1-5.15.1-cp35.cp36.cp37.cp38.cp39-abi3-manylinux1_x86_64.whl", hash = "sha256:71f563daabc3a363ccc27a843cfb452f01ebaeb24e5e46704964ffa4c7dc98d9"},
+    {file = "shiboken2-5.15.1-5.15.1-cp35.cp36.cp37.cp38.cp39-none-win32.whl", hash = "sha256:4c112b5758a39ffa5473d2c6995e2594eb6e002332d9dac35e2efd9debf157a2"},
+    {file = "shiboken2-5.15.1-5.15.1-cp35.cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:55249cad55541471b60256179d92be08d520873b76f9cff595bae775dbb06aba"},
+]
+single-source = [
+    {file = "single-source-0.2.0.tar.gz", hash = "sha256:f40f94c7f2e72c854b9c0c6f7d6b545d74ada9ebd454f9f07f8fb743b22bccf5"},
+    {file = "single_source-0.2.0-py3-none-any.whl", hash = "sha256:82c55b00515a30c8f7c262b986a399ca023911ebb6a86f0953d4c50448c36b16"},
+]
+tqdm = [
+    {file = "tqdm-4.51.0-py2.py3-none-any.whl", hash = "sha256:9ad44aaf0fc3697c06f6e05c7cf025dd66bc7bcb7613c66d85f4464c47ac8fad"},
+    {file = "tqdm-4.51.0.tar.gz", hash = "sha256:ef54779f1c09f346b2b5a8e5c61f96fbcb639929e640e59f8cf810794f406432"},
+]
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..2163f8d
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,27 @@
+[tool.poetry]
+name = "dstat-interface"
+version = "2.0.0"
+description = "Interface for the DStat potentiostat (https://microfluidics.utoronto.ca/dstat)."
+authors = ["Michael DM Dryden <mk.dryden@utoronto.ca>"]
+license = "gpl-3.0-or-later"
+
+[tool.poetry.dependencies]
+python = ">=3.8,<3.10"
+PySide2 = "^5.15.1"
+pyserial = "^3.4"
+pyzmq = "^19.0.2"
+PyYAML = "^5.3.1"
+numpy = "^1.19.2"
+appdirs = "^1.4.4"
+tqdm = "^4.51.0"
+single-source = "^0.2.0"
+
+[tool.poetry.dev-dependencies]
+
+[tool.poetry-dynamic-versioning]
+enable = true
+vcs = "git"
+
+[build-system]
+requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"]
+build-backend = "poetry.core.masonry.api"
diff --git a/version.py b/version.py
deleted file mode 100644
index ae2f3d2..0000000
--- a/version.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""Calculates the current version number.
-
-If possible, uses output of “git describe” modified to conform to the
-visioning scheme that setuptools uses (see PEP 386).  Releases must be
-labelled with annotated tags (signed tags are annotated) of the following
-format:
-
-   v<num>(.<num>)+ [ {a|b|c|rc} <num> (.<num>)* ]
-
-If “git describe” returns an error (likely because we're in an unpacked copy
-of a release tarball, rather than a git working copy), or returns a tag that
-does not match the above format, version is read from RELEASE-VERSION file.
-
-To use this script, simply import it your setup.py file, and use the results
-of getVersion() as your package version:
-
-    import version
-    setup(
-        version=version.getVersion(),
-        .
-        .
-        .
-    )
-
-This will automatically update the RELEASE-VERSION file.  The RELEASE-VERSION
-file should *not* be checked into git but it *should* be included in sdist
-tarballs (as should version.py file).  To do this, run:
-
-    echo include RELEASE-VERSION version.py >>MANIFEST.in
-    echo RELEASE-VERSION >>.gitignore
-
-With that setup, a new release can be labelled by simply invoking:
-
-    git tag -s v1.0
-"""
-
-__author__ = ('Douglas Creager <dcreager@dcreager.net>',
-              'Michal Nazarewicz <mina86@mina86.com>')
-__license__ = 'This file is placed into the public domain.'
-__maintainer__ = 'Michal Nazarewicz'
-__email__ = 'mina86@mina86.com'
-
-__all__ = ('getVersion')
-
-
-import re
-import subprocess
-import sys
-import os.path
-import inspect
-
-RELEASE_VERSION_FILE = '{}/RELEASE-VERSION'.format(
-    os.path.dirname(os.path.abspath(inspect.stack()[0][1])))
-
-# http://www.python.org/dev/peps/pep-0386/
-_PEP386_SHORT_VERSION_RE = r'\d+(?:\.\d+)+(?:(?:[abc]|rc)\d+(?:\.\d+)*)?'
-_PEP386_VERSION_RE = r'^%s(?:\.post\d+)?(?:\.dev\d+)?$' % (
-    _PEP386_SHORT_VERSION_RE)
-_GIT_DESCRIPTION_RE = r'^v(?P<ver>%s)-(?P<commits>\d+)-g(?P<sha>[\da-f]+)$' % (
-    _PEP386_SHORT_VERSION_RE)
-
-
-def readGitVersion():
-    try:
-        proc = subprocess.Popen(('git', 'describe', '--long',
-                                 '--match', 'v[0-9]*.*'),
-                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-        data, _ = proc.communicate()
-        if proc.returncode:
-            return None
-        ver = data.splitlines()[0].strip()
-        proc = subprocess.Popen(('git', 'rev-parse', '--abbrev-ref', 'HEAD'),
-                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-        branch, _ = proc.communicate()
-        if proc.returncode:
-            return None
-    except:
-        return None
-
-    if not ver:
-        return None
-    m = re.search(_GIT_DESCRIPTION_RE, ver)
-    if not m:
-        sys.stderr.write('version: git description (%s) is invalid, '
-                         'ignoring\n' % ver)
-        return None
-
-    commits = int(m.group('commits'))
-
-    if not commits:
-        version = m.group('ver')
-    else:
-        version = '%s.post%d' % (
-            m.group('ver'), commits)
-
-    if branch.strip() != 'master':
-        version += '.dev%d' % int(m.group('sha'), 16)
-
-    return version
-
-
-def readReleaseVersion():
-    try:
-        fd = open(RELEASE_VERSION_FILE)
-        try:
-            ver = fd.readline().strip()
-        finally:
-            fd.close()
-        if not re.search(_PEP386_VERSION_RE, ver):
-            sys.stderr.write('version: release version (%s) is invalid, '
-                             'will use it anyway\n' % ver)
-        return ver
-    except:
-        return None
-
-
-def writeReleaseVersion(version):
-    fd = open(RELEASE_VERSION_FILE, 'w')
-    fd.write('%s\n' % version)
-    fd.close()
-
-
-def getVersion():
-    release_version = readReleaseVersion()
-    version = readGitVersion() or release_version
-    if not version:
-        raise ValueError('Cannot find the version number')
-    if version != release_version:
-        writeReleaseVersion(version)
-    return version
-
-
-if __name__ == '__main__':
-    print getVersion()
-
-- 
GitLab