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