diff --git a/dstat_interface/dstat_comm.py b/dstat_interface/dstat_comm.py index cc81ce3418c091e3cd47ef2ee534e7ca1db4e5fa..a468d47315b8d124a6f9664059520c9cecf6936b 100755 --- a/dstat_interface/dstat_comm.py +++ b/dstat_interface/dstat_comm.py @@ -22,24 +22,47 @@ from serial.tools import list_ports import time import struct import multiprocessing as mp +from collections import OrderedDict 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 errors import InputError, VarError logger = logging.getLogger("dstat.comm") dstat_logger = logging.getLogger("dstat.comm.DSTAT") exp_logger = logging.getLogger("dstat.comm.Experiment") -serial_instance = None -settings = {} +import state + +class AlreadyConnectedError(Exception): + def __init__(self): + super(AlreadyConnectedError, self).__init__(self, + "Serial instance already connected.") + +class NotConnectedError(Exception): + def __init__(self): + super(NotConnectedError, self).__init__(self, + "Serial instance not connected.") def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe): ser_logger = logging.getLogger("dstat.comm._serial_process") - ser = delayedSerial(ser_port, baudrate=1000000, timeout=1) - ser_logger.info("Reattaching DStat udc") - ser.write("!R") # Send restart command - ser.close() + connected = False + + try: + ser = delayedSerial(ser_port, baudrate=1000000, timeout=1) + ser_logger.info("Reattaching DStat udc") + # ser.write("!R") # Send restart command + ser.close() + except serial.SerialException: + return 1 for i in range(5): time.sleep(1) # Give OS time to enumerate @@ -47,9 +70,15 @@ def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe): try: ser = delayedSerial(ser_port, baudrate=1000000, timeout=1) ser_logger.info("Connecting") - break + connected = True except serial.SerialException: pass + + if connected is True: + break + + if ser.isOpen() is False: + return 1 ser.write("ck") # Keep this to support old firmwares @@ -78,7 +107,7 @@ def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe): ser_logger.info("DISCONNECT") ser.close() proc_pipe.send("DISCONNECT") - return False + return 0 elif proc_pipe.poll(): while ctrl_pipe.poll(): @@ -94,19 +123,91 @@ def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe): -class SerialConnection(object): - def __init__(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 = mp.Process(target=_serial_process, args=(ser_port, - self.proc_pipe_c, self.ctrl_pipe_c, - self.data_pipe_c)) - self.proc.start() +class SerialConnection(GObject.GObject): + __gsignals__ = { + 'connected': (GObject.SIGNAL_RUN_FIRST, None, ()), + 'disconnected': (GObject.SIGNAL_RUN_FIRST, None, ()) + } + + def __init__(self): + super(SerialConnection, self).__init__() + self.connected = False + + def connect(self, ser_port): + 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.start() + time.sleep(.5) + 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): + self.assert_connected() + + self.proc_pipe_p.send(exp) + + def stop_exp(self): + self.assert_connected() + self.send_ctrl('a') + def get_proc(self, block=False): + self.assert_connected() + + if block is True: + return self.proc_pipe_p.recv() + else: + if self.proc_pipe_p.poll() is True: + return self.proc_pipe_p.recv() + else: + return None + + def get_data(self, block=False): + self.assert_connected() + + if block is True: + return self.data_pipe_p.recv() + else: + if self.data_pipe_p.poll() is True: + return self.data_pipe_p.recv() + else: + return None + + def flush_data(self): + self.assert_connected() + + while self.proc_pipe_p.poll() is True: + self.proc_pipe_p.recv() + + def send_ctrl(self, ctrl): + self.assert_connected() + + self.ctrl_pipe_p.send(ctrl) + + def disconnect(self): + self.send_ctrl('a') + time.sleep(.2) + self.proc.terminate() + self.emit('disconnected') + self.connected = False -class VersionCheck: +class VersionCheck(object): def __init__(self): pass @@ -160,24 +261,24 @@ def version_check(ser_port): Arguments: ser_port -- address of serial port to use """ - try: - global serial_instance - serial_instance = SerialConnection(ser_port) - - serial_instance.proc_pipe_p.send(VersionCheck()) - result = serial_instance.proc_pipe_p.recv() - if result == "SERIAL_ERROR": - buffer = 1 - else: - buffer = serial_instance.data_pipe_p.recv() - logger.debug("version_check done") - - return buffer + # try: + state.ser = SerialConnection() + + state.ser.connect(ser_port) + state.ser.start_exp(VersionCheck()) + result = state.ser.get_proc(block=True) + if result == "SERIAL_ERROR": + buffer = 1 + else: + buffer = state.ser.get_data(block=True) + logger.debug("version_check done") + + return buffer - except: - pass + # except: + # pass -class Settings: +class Settings(object): def __init__(self, task, settings=None): self.task = task self.settings = settings @@ -200,7 +301,7 @@ class Settings: return status def read(self): - settings = {} + settings = OrderedDict() self.ser.flushInput() self.ser.write('!') @@ -252,16 +353,11 @@ def read_settings(): settings. """ - global settings - settings = {} - - while serial_instance.data_pipe_p.poll(): - serial_instance.data_pipe_p.recv() - - serial_instance.proc_pipe_p.send(Settings(task='r')) - settings = serial_instance.data_pipe_p.recv() + state.ser.flush_data() + state.ser.start_exp(Settings(task='r')) + state.settings = state.ser.get_data(block=True) - logger.debug("read_settings: %s", serial_instance.proc_pipe_p.recv()) + logger.debug("read_settings: %s", state.ser.get_proc(block=True)) return @@ -269,12 +365,10 @@ def write_settings(): """Tries to write settings to DStat from global settings var. """ - while serial_instance.data_pipe_p.poll(): - serial_instance.data_pipe_p.recv() + state.ser.flush_data() + state.ser.start_exp(Settings(task='w', settings=state.settings)) - serial_instance.proc_pipe_p.send(Settings(task='w', settings=settings)) - - logger.debug("write_settings: %s", serial_instance.proc_pipe_p.recv()) + logger.debug("write_settings: %s", state.ser.get_proc(block=True)) return @@ -317,14 +411,12 @@ def read_light_sensor(): light sensor clear channel. """ - while serial_instance.data_pipe_p.poll(): - serial_instance.data_pipe_p.recv() - - serial_instance.proc_pipe_p.send(LightSensor()) + state.ser.flush_data() + state.ser.start_exp(LightSensor()) - logger.info("read_light_sensor: %s", serial_instance.proc_pipe_p.recv()) + logger.debug("read_light_sensor: %s", state.ser.get_proc(block=True)) - return serial_instance.data_pipe_p.recv() + return state.ser.get_data(block=True) class delayedSerial(serial.Serial): diff --git a/dstat_interface/experiments/cal.py b/dstat_interface/experiments/cal.py index f57c26f6d50fc9c9e37cd0db98bff87c9bc632c7..9f965b7213b644403f55740704f45ff2f1b38505 100755 --- a/dstat_interface/experiments/cal.py +++ b/dstat_interface/experiments/cal.py @@ -28,7 +28,7 @@ import serial logger = logging.getLogger("dstat.experiments.cal") -from dstat_comm import serial_instance +import state from experiments.experiment_template import Experiment def measure_offset(time): @@ -42,9 +42,9 @@ def measure_offset(time): for i in range(1,8): parameters['gain'] = i - serial_instance.proc_pipe_p.send(CALExp(parameters)) - logger.info("measure_offset: %s", serial_instance.proc_pipe_p.recv()) - gain_offset[gain_trim_table[i]] = serial_instance.data_pipe_p.recv() + 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 diff --git a/dstat_interface/experiments/experiment_template.py b/dstat_interface/experiments/experiment_template.py index 96888d4a78c884e59790df5b3bef1510fd4af632..47a49a1ace201ce6735646d6643d96d45978269e 100755 --- a/dstat_interface/experiments/experiment_template.py +++ b/dstat_interface/experiments/experiment_template.py @@ -53,7 +53,7 @@ dstat_logger = logging.getLogger("dstat.comm.DSTAT") exp_logger = logging.getLogger("dstat.comm.Experiment") from errors import InputError, VarError -import dstat_comm +import state class Experiment(object): """Store and acquire a potentiostat experiment. Meant to be subclassed @@ -87,7 +87,7 @@ class Experiment(object): self.gain = self.__gaintable[int(self.parameters['gain'])] self.gain_trim = int( - dstat_comm.settings[ + state.settings[ self.__gain_trim_table[int(self.parameters['gain'])] ][1] ) diff --git a/dstat_interface/interface/exp_int.py b/dstat_interface/interface/exp_int.py index caa4a58222b0fa7b4d65a3ccc55f5bbd3dd0c0b4..3e1cb6790db67a248aaf391be83c32b4acab03d7 100755 --- a/dstat_interface/interface/exp_int.py +++ b/dstat_interface/interface/exp_int.py @@ -31,6 +31,7 @@ except ImportError: sys.exit(1) import dstat_comm +import state import experiments as exp import experiments.cal as cal import __main__ @@ -406,11 +407,11 @@ class PD(ExpInterface): self.builder.get_object('light_label').set_text(str( dstat_comm.read_light_sensor())) dstat_comm.read_settings() - dstat_comm.settings['tcs_enabled'][1] = '1' # Make sure TCS enabled + state.settings['tcs_enabled'][1] = '1' # Make sure TCS enabled dstat_comm.write_settings() self.builder.get_object('threshold_entry').set_text(str( - dstat_comm.settings['tcs_clear_threshold'][1])) + state.settings['tcs_clear_threshold'][1])) __main__.MAIN.start_ocp() finally: @@ -424,12 +425,12 @@ class PD(ExpInterface): i.set_sensitive(False) try: - dstat_comm.settings['tcs_clear_threshold'][1] = self.builder.get_object( + state.settings['tcs_clear_threshold'][1] = self.builder.get_object( 'threshold_entry').get_text() dstat_comm.write_settings() dstat_comm.read_settings() self.builder.get_object('threshold_entry').set_text( - str(dstat_comm.settings['tcs_clear_threshold'][1])) + str(state.settings['tcs_clear_threshold'][1])) __main__.MAIN.start_ocp() finally: @@ -487,19 +488,19 @@ class CAL(ExpInterface): dstat_comm.read_settings() self.entry['R100'].set_text(str( - dstat_comm.settings['r100_trim'][1])) + state.settings['r100_trim'][1])) self.entry['R3k'].set_text(str( - dstat_comm.settings['r3k_trim'][1])) + state.settings['r3k_trim'][1])) self.entry['R30k'].set_text(str( - dstat_comm.settings['r30k_trim'][1])) + state.settings['r30k_trim'][1])) self.entry['R300k'].set_text(str( - dstat_comm.settings['r300k_trim'][1])) + state.settings['r300k_trim'][1])) self.entry['R3M'].set_text(str( - dstat_comm.settings['r3M_trim'][1])) + state.settings['r3M_trim'][1])) self.entry['R30M'].set_text(str( - dstat_comm.settings['r30M_trim'][1])) + state.settings['r30M_trim'][1])) self.entry['R100M'].set_text(str( - dstat_comm.settings['r100M_trim'][1])) + state.settings['r100M_trim'][1])) __main__.MAIN.start_ocp() @@ -514,13 +515,13 @@ class CAL(ExpInterface): __main__.MAIN.on_pot_stop_clicked() __main__.MAIN.stop_ocp() - dstat_comm.settings['r100_trim'][1] = self.entry['R100'].get_text() - dstat_comm.settings['r3k_trim'][1] = self.entry['R3k'].get_text() - dstat_comm.settings['r30k_trim'][1] = self.entry['R30k'].get_text() - dstat_comm.settings['r300k_trim'][1] = self.entry['R300k'].get_text() - dstat_comm.settings['r3M_trim'][1] = self.entry['R3M'].get_text() - dstat_comm.settings['r30M_trim'][1] = self.entry['R30M'].get_text() - dstat_comm.settings['r100M_trim'][1] = self.entry['R100M'].get_text() + 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() dstat_comm.write_settings() __main__.MAIN.start_ocp() @@ -544,22 +545,22 @@ class CAL(ExpInterface): for i in offset: logger.info("{} {}".format(i, str(-offset[i]))) - dstat_comm.settings[i][1] = str(-offset[i]) + state.settings[i][1] = str(-offset[i]) self.entry['R100'].set_text(str( - dstat_comm.settings['r100_trim'][1])) + state.settings['r100_trim'][1])) self.entry['R3k'].set_text(str( - dstat_comm.settings['r3k_trim'][1])) + state.settings['r3k_trim'][1])) self.entry['R30k'].set_text(str( - dstat_comm.settings['r30k_trim'][1])) + state.settings['r30k_trim'][1])) self.entry['R300k'].set_text(str( - dstat_comm.settings['r300k_trim'][1])) + state.settings['r300k_trim'][1])) self.entry['R3M'].set_text(str( - dstat_comm.settings['r3M_trim'][1])) + state.settings['r3M_trim'][1])) self.entry['R30M'].set_text(str( - dstat_comm.settings['r30M_trim'][1])) + state.settings['r30M_trim'][1])) self.entry['R100M'].set_text(str( - dstat_comm.settings['r100M_trim'][1])) + state.settings['r100M_trim'][1])) __main__.MAIN.start_ocp() finally: diff --git a/dstat_interface/main.py b/dstat_interface/main.py index f30fbff0ac305142b14d48862c138f4f6f45acdd..5621e2e024b878b64be69d27354f817073e1c2ef 100755 --- a/dstat_interface/main.py +++ b/dstat_interface/main.py @@ -59,6 +59,7 @@ import params import parameter_test import analysis import zmq +import state from errors import InputError from plugin import DstatPlugin, get_hub_uri @@ -258,8 +259,8 @@ class Main(object): self.stop_ocp() else: self.on_pot_stop_clicked() - comm.serial_instance.ctrl_pipe_p.send("DISCONNECT") - comm.serial_instance.proc.terminate() + state.ser.send_ctrl("DISCONNECT") + state.ser.disconnect() except AttributeError as err: logger.warning("AttributeError: %s", err) @@ -284,16 +285,15 @@ class Main(object): if self.version[0] >= 1 and self.version[1] >= 2: # Flush data pipe - while comm.serial_instance.data_pipe_p.poll(): - comm.serial_instance.data_pipe_p.recv() + state.ser.flush_data() if self.pmt_mode == True: logger.info("Start PMT idle mode") - comm.serial_instance.proc_pipe_p.send(exp.PMTIdle()) + state.ser.start_exp(exp.PMTIdle()) else: logger.info("Start OCP") - comm.serial_instance.proc_pipe_p.send(exp.OCPExp()) + state.ser.start_exp(exp.OCPExp()) self.ocp_proc = (GObject.timeout_add(300, self.ocp_running_data), GObject.timeout_add(250, self.ocp_running_proc) @@ -312,7 +312,7 @@ class Main(object): logger.info("Stop PMT idle mode") else: logger.info("Stop OCP") - comm.serial_instance.ctrl_pipe_p.send('a') + state.ser.send_ctrl('a') for i in self.ocp_proc: GObject.source_remove(i) @@ -334,9 +334,8 @@ class Main(object): """ try: - if comm.serial_instance.data_pipe_p.poll(): - incoming = comm.serial_instance.data_pipe_p.recv() - + incoming = 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 @@ -346,9 +345,7 @@ class Main(object): " V"]) self.ocp_disp.set_text(data) - if comm.serial_instance.data_pipe_p.poll(): - self.ocp_running_data() - return True + incoming = state.ser.get_data() return True @@ -367,19 +364,16 @@ class Main(object): """ try: - if comm.serial_instance.proc_pipe_p.poll(): - proc_buffer = comm.serial_instance.proc_pipe_p.recv() + proc_buffer = 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() - while comm.serial_instance.data_pipe_p.poll(): - comm.serial_instance.data_pipe_p.recv() + state.ser.flush_data() return False - - return True - + proc_buffer = state.ser.get_proc() return True except EOFError: @@ -417,8 +411,7 @@ class Main(object): self.stop_ocp() self.statusbar.remove_all(self.error_context_id) - while comm.serial_instance.data_pipe_p.poll(): # Clear data pipe - comm.serial_instance.data_pipe_p.recv() + state.ser.flush_data() parameters = {} parameters['version'] = self.version @@ -454,11 +447,10 @@ class Main(object): self.data_view.clear_exps() self.info_page.clear() - comm.serial_instance.proc_pipe_p.send(self.current_exp) + state.ser.start_exp(self.current_exp) # Flush data pipe - while comm.serial_instance.data_pipe_p.poll(): - comm.serial_instance.data_pipe_p.recv() + state.ser.flush_data() self.plot_proc = GObject.timeout_add(200, self.experiment_running_plot) @@ -515,22 +507,19 @@ class Main(object): function from GTK's queue. """ try: - if comm.serial_instance.data_pipe_p.poll(): - incoming = comm.serial_instance.data_pipe_p.recv() - - self.line, data = incoming - if self.line > self.lastdataline: - newline = True - self.lastdataline = self.line - else: - newline = False - - self.current_exp.store_data(incoming, newline) - - if comm.serial_instance.data_pipe_p.poll(): - self.experiment_running_data() - return True - + incoming = state.ser.get_data() + while incoming is not None: + try: + self.line, data = incoming + if self.line > self.lastdataline: + newline = True + 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: @@ -552,9 +541,8 @@ class Main(object): function from GTK's queue. """ try: - if comm.serial_instance.proc_pipe_p.poll(): - proc_buffer = comm.serial_instance.proc_pipe_p.recv() - + 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": @@ -563,9 +551,7 @@ class Main(object): else: logger.warning("Unrecognized experiment return code: %s", proc_buffer) - return False - return True except EOFError as err: @@ -687,7 +673,7 @@ class Main(object): def on_pot_stop_clicked(self, data=None): """Stop current experiment. Signals experiment process to stop.""" try: - comm.serial_instance.ctrl_pipe_p.send('a') + state.ser.stop_exp() except AttributeError: pass diff --git a/dstat_interface/state.py b/dstat_interface/state.py new file mode 100644 index 0000000000000000000000000000000000000000..74295bdcf2e199a1a134ad9af1464b19d6890054 --- /dev/null +++ b/dstat_interface/state.py @@ -0,0 +1,4 @@ +from collections import OrderedDict + +settings = OrderedDict() +ser = None \ No newline at end of file