From a62ee801449780011fe0ecbd8afbca7186218647 Mon Sep 17 00:00:00 2001 From: "Michael D. M. Dryden" <mdryden@chem.utoronto.ca> Date: Wed, 6 Aug 2014 17:18:47 -0400 Subject: [PATCH] Microdrop interface using zmq. Currently defaults to listening on all interfaces for TCP port 6789. --- .../dstatInterface.xcodeproj/project.pbxproj | 2 + dstatInterface/interface/dstatinterface.glade | 17 ++-- dstatInterface/interface_test.py | 93 +++++++++++++++---- dstatInterface/microdrop.py | 51 ++++++++++ 4 files changed, 139 insertions(+), 24 deletions(-) create mode 100644 dstatInterface/microdrop.py diff --git a/dstatInterface/dstatInterface.xcodeproj/project.pbxproj b/dstatInterface/dstatInterface.xcodeproj/project.pbxproj index 6371382..dccb0b5 100644 --- a/dstatInterface/dstatInterface.xcodeproj/project.pbxproj +++ b/dstatInterface/dstatInterface.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXFileReference section */ 5F87883C19072E86007B53E0 /* mpltest.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = mpltest.py; sourceTree = "<group>"; }; + 5FB0B8E1198ACD4B00FA6CB7 /* microdrop.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = microdrop.py; sourceTree = "<group>"; }; 5FCB541B190591CD00CEB148 /* interface */ = {isa = PBXFileReference; lastKnownFileType = folder; path = interface; sourceTree = "<group>"; }; 5FCB541D1905923800CEB148 /* interface_test.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = interface_test.py; sourceTree = "<group>"; }; 5FCB54231905B6EE00CEB148 /* dstat_comm.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = dstat_comm.py; sourceTree = "<group>"; }; @@ -23,6 +24,7 @@ 5FF00FDC1942BD16004D38A8 /* setup.py */, 5F87883C19072E86007B53E0 /* mpltest.py */, 5FCB541D1905923800CEB148 /* interface_test.py */, + 5FB0B8E1198ACD4B00FA6CB7 /* microdrop.py */, 5FCB54231905B6EE00CEB148 /* dstat_comm.py */, 5FCB541B190591CD00CEB148 /* interface */, 5FDC0DFD18FDAD79003F857A /* mpl.py */, diff --git a/dstatInterface/interface/dstatinterface.glade b/dstatInterface/interface/dstatinterface.glade index a608eaa..6b1062a 100644 --- a/dstatInterface/interface/dstatinterface.glade +++ b/dstatInterface/interface/dstatinterface.glade @@ -174,31 +174,32 @@ </object> </child> <child> - <object class="GtkMenuItem" id="menuitem2"> + <object class="GtkMenuItem" id="menu_dropbot"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="label" translatable="yes">_Edit</property> + <property name="label" translatable="yes">Dropbot</property> <property name="use_underline">True</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="imagemenuitem7"> - <property name="label">gtk-copy</property> + <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="imagemenuitem8"> - <property name="label">gtk-paste</property> + <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> diff --git a/dstatInterface/interface_test.py b/dstatInterface/interface_test.py index e6d74e8..6daf7d7 100644 --- a/dstatInterface/interface_test.py +++ b/dstatInterface/interface_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- import sys try: @@ -34,6 +35,7 @@ import multiprocessing import time import mpltest +import microdrop class Error(Exception): pass @@ -76,6 +78,7 @@ class main: self.pd = pd.pd() self.error_context_id = self.statusbar.get_context_id("error") + self.message_context_id = self.statusbar.get_context_id("message") self.plotwindow = self.builder.get_object('plotbox') @@ -143,6 +146,11 @@ class main: self.pd_container.hide() self.expnumber = 0 + + self.menu_dropbot_connect = self.builder.get_object('menu_dropbot_connect') + self.menu_dropbot_disconnect = self.builder.get_object('menu_dropbot_disconnect') + self.dropbot_enabled = False + self.dropbot_triggered = False def exp_param_show(self, selection): self.chronoamp_container.hide() @@ -196,6 +204,18 @@ class main: self.serial_liststore.append([i]) def on_pot_start_clicked(self, data=None): + + def exceptions(): #cleans up after errors + if self.dropbot_enabled == True: + if self.dropbot_triggered == True: + self.dropbot_triggered = False + print "finallydone" + self.microdrop.reply(microdrop.EXPFINISHED) + self.microdrop_proc = gobject.timeout_add(500, self.microdrop_listen) + self.spinner.stop() + self.startbutton.set_sensitive(True) + self.stopbutton.set_sensitive(False) + selection = self.expcombobox.get_active() parameters = {} view_parameters = {} @@ -247,6 +267,7 @@ class main: self.plot_proc = gobject.timeout_add(200, self.experiment_running_plot) gobject.idle_add(self.experiment_running) + return elif selection == 1: #LSV parameters['clean_mV'] = int(self.lsv.clean_mV.get_text()) @@ -292,6 +313,7 @@ class main: self.plot_proc = gobject.timeout_add(200, self.experiment_running_plot) gobject.idle_add(self.experiment_running) + return elif selection == 2: #CV parameters['clean_mV'] = int(self.cv.clean_mV.get_text()) @@ -343,6 +365,7 @@ class main: self.plot_proc = gobject.timeout_add(200, self.experiment_running_plot) gobject.idle_add(self.experiment_running) + return elif selection == 3: #SWV parameters['clean_mV'] = int(self.swv.clean_mV.get_text()) @@ -401,6 +424,7 @@ class main: self.plot_proc = gobject.timeout_add(200, self.experiment_running_plot) gobject.idle_add(self.experiment_running) + return elif selection == 4: #DPV parameters['clean_mV'] = int(self.dpv.clean_mV.get_text()) @@ -457,35 +481,27 @@ class main: self.plot_proc = gobject.timeout_add(200, self.experiment_running_plot) gobject.idle_add(self.experiment_running) + return else: self.statusbar.push(self.error_context_id, "Experiment not yet implemented.") - self.startbutton.set_sensitive(True) - self.stopbutton.set_sensitive(False) + exceptions() except ValueError: - self.spinner.stop() self.statusbar.push(self.error_context_id, "Experiment parameters must be integers.") - self.startbutton.set_sensitive(True) - self.stopbutton.set_sensitive(False) + exceptions() except InputError as e: - self.spinner.stop() self.statusbar.push(self.error_context_id, e.msg) - self.startbutton.set_sensitive(True) - self.stopbutton.set_sensitive(False) + exceptions() except SerialException: - self.spinner.stop() self.statusbar.push(self.error_context_id, "Could not establish serial connection.") - self.startbutton.set_sensitive(True) - self.stopbutton.set_sensitive(False) + exceptions() except AssertionError as e: - self.spinner.stop() self.statusbar.push(self.error_context_id, str(e)) - self.startbutton.set_sensitive(True) - self.stopbutton.set_sensitive(False) + exceptions() def experiment_running(self): try: @@ -534,7 +550,6 @@ class main: self.rawbuffer.insert_at_cursor(str(row)+ " ") self.rawbuffer.insert_at_cursor("\n") - if self.current_exp.data_extra: for col in zip(*self.current_exp.data_extra): for row in col: @@ -546,6 +561,13 @@ class main: save.autoPlot(self.plot, self.autosavedir_button, self.autosavename.get_text(), self.expnumber) self.expnumber += 1 + if self.dropbot_enabled == True: + if self.dropbot_triggered == True: + self.dropbot_triggered = False + print "expdone" + self.microdrop.reply(microdrop.EXPFINISHED) + self.microdrop_proc = gobject.timeout_add(500, self.microdrop_listen) + self.spinner.stop() self.startbutton.set_sensitive(True) self.stopbutton.set_sensitive(False) @@ -560,7 +582,46 @@ class main: save_inst = save.manSave(self.current_exp) def on_file_save_plot_activate(self, menuitem, data=None): - save_inst = save.plotSave(self.plot) + save_inst = save.plotSave(self.plot) + + def on_menu_dropbot_connect_activate(self, menuitem, data=None): + self.microdrop = microdrop.microdropConnection() + self.dropbot_enabled = True + self.menu_dropbot_connect.set_sensitive(False) + self.menu_dropbot_disconnect.set_sensitive(True) + self.statusbar.push(self.message_context_id, "Waiting for µDrop to connect…") + self.microdrop_proc = gobject.timeout_add(500, self.microdrop_listen) + + def on_menu_dropbot_disconnect_activate(self, menuitem, data= None): + self.microdrop.reset() + del self.microdrop + self.dropbot_enabled = False + self.menu_dropbot_connect.set_sensitive(True) + self.menu_dropbot_disconnect.set_sensitive(False) + + def microdrop_listen(self): + drdy, data = self.microdrop.listen() + if drdy == False: + return True + if self.microdrop.connected == False: + if data == microdrop.CONREQ: + print "INFO: µDrop connected" + self.statusbar.push(self.message_context_id, "µDrop connected.") + self.microdrop.reply(microdrop.CONREP) + self.microdrop.connected = True + else: + print "WAR: Invalid µDrop connection request." + self.microdrop.reply(microdrop.INVAL_CMD) + elif data == microdrop.STARTEXP: + self.dropbot_triggered = True + self.on_pot_start_clicked() + return False + else: + print "WAR: Received invalid command from µDrop" + self.microdrop.reply(microdrop.INVAL_CMD) + return True + + if __name__ == "__main__": diff --git a/dstatInterface/microdrop.py b/dstatInterface/microdrop.py new file mode 100644 index 0000000..95ea34c --- /dev/null +++ b/dstatInterface/microdrop.py @@ -0,0 +1,51 @@ +import zmq +import zmq.error + +#signals +CONREQ = "0" +CONREP = "1" +STARTEXP = "10" +EXPFINISHED = "11" +INVAL_CMD = "99" + +#States +RECV = 0 +SEND = 1 + +class microdropConnection: + + def __init__(self, port=6789): + 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): + if self.state == SEND: + print "WAR: Microdrop Connection state invalid, resetting..." + self.reset() + self.__init__(self.port) + 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): + if self.state == RECV: + print "WAR: Microdrop Connection state invalid, resetting..." + self.reset() + self.__init__(self.port) + return False + self.state = RECV + self.soc.send(data) + return True + + def reset(self): + self.soc.unbind("".join(['tcp://*:',str(self.port)])) + del self.soc + del self.ctx -- GitLab