1

I am commanding a couple devices over serial from a tkinter based GUI, and generally I just want a long process of making sure all the equipment is healthy to run by itself. But occasionally if I see something strange while this process is running I want to interrupt it, send a few manual commands, and then get it back on track.

So, given the code below, I want the process to look like:

  • hit pause and the application stops commanding all the devices and kills their serial connections

  • the user "engages manual mode" and then they can command the devices however they want

  • then the user will resume the calibration.

Is there an obvious way to do this given my current setup?

I would like to avoid multiple threads, but I'm not totally opposed to threading. I need access to the ec1 and ec2 objects from within calibrate_devices() in order to safely shut down their serial connections, but the idea of having a bunch of if statements in there to see if pause has been hit seems like an especially clunky solution.

import Tkinter as tk
import ttk
import serial
import time  

class Equipment_Controller_One():
    def __init__(self):
        # create serial connection, etc

    def turn_on(self):
        # command equipment on

    def is_healthy(self):
        # run through lots of tests here

    def kill(self):

    # all other necessary functions

class Equipment_Controller_Two():
    def __init__(self):
        # create serial connection, etc

    def turn_on(self):
        # command equipment on

    def is_healthy(self):
        # run through lots of tests here

    def kill(self):

    # all other necessary functions

class Application(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.grid()
        print("Firing up the GUI window.")

        ### creating all application variables
        self.status = tk.StringVar()
        self.status.set("Awaiting instruction.")
        self.details = tk.StringVar()
        self.details.set("Please initiate a command.")
        self.errors = tk.StringVar()
        self.errors.set("No errors yet detected.")

        self.createWidgets()

    def createWidgets(self):
        ttk.Label(self, text="Manual Commands").grid(column=5, row=1)

        ttk.Label(self, text="STATUS:").grid(column=1, row=8)
        ttk.Label(self, textvariable=self.status).grid(column=2, row=8, columnspan=3)

        ttk.Label(self, text="Details:").grid(column=1, row=9)
        ttk.Label(self, textvariable=self.details).grid(column=2, row=9, columnspan=3)

        ttk.Label(self, text="Errors:").grid(column=1, row=10)
        ttk.Label(self, textvariable=self.errors).grid(column=2, row=10, columnspan=3)

        # buttons
        ttk.Button(self, text="Set Temp", command=self.setTemp).grid(column=6, row=5)
        ttk.Button(self, text="Begin Calibration", command=self.calibrate).grid(column=4, row=3)
        ttk.Button(self, text="Pause Calibration", command=self.pause).grid(column=4, row=4)
        ttk.Button(self, text="Engage Manual Mode", command=self.engageManual).grid(column=4, row=5)

    def abortProcedure(self, my_string):
        # safely exit everything here
        self.quit()

    def calibrate(self):
        calibrate_devices(self)

    def pause(self):
        self.status.set("Calibration paused.")
        # when this button is hit I want to command all equipment to stop what they're doing

    def engageManual(self):
        self.status.set("Manual mode engaged.")

        # run through any safety checks here to make sure it is in manual

        self.in_manual = True

    def setTemp(self):
        if not self.in_manual:
            # report some error and don't proceed
            self.errors.set("ERROR: Not safely in manual mode.")
            return
        ec = Equipment_Controller_One()
        bc.turn_on()
        self.details.set("Manully commanding device one.")
        # run through some commands here like setting the temperature on device one



def calibrate_devices(app):
    app.status.set("Calibration initiated.")

    # begin commanding equipment

    ec1 = Equipment_Controller_One()

    # this takes a long time to run
    if (ec1.is_healthy() == True):
        # mark as healthy
    else:
        # something is wrong and neet to trip a pause and alarm and wait for input
        app.errors.set("Device One is unhealthy, stopping calibration.")

    ec2 = Equipment_Controller_Two()

    # this takes a long time to run
    if (ec2.is_healthy() == True):
        # mark as healthy
    else:
        # something is wrong and neet to trip a pause and alarm and wait for input
        app.errors.set("Device Two is unhealthy, stopping calibration.")


def main():
    app = Application()         
    app.master.title('Equipment Calibration')

    app.mainloop()    

if __name__ == '__main__':
    main()

Any ideas are very welcome!

clifgray
  • 4,313
  • 11
  • 67
  • 116
  • The simplest way is to refactor the code such that you can both - run a bunch of tests and check for a user interrupt between each test, in a single place. This is assuming you're ok with the interrupt granularity being 'a single test'. – pvg Mar 07 '17 at 18:27
  • Hmm, well a single test in this case can run for 5 to 10 minutes. It isn't the end of the world if I can't interrupt mid-test but I would like to be able to. – clifgray Mar 07 '17 at 19:14
  • You haven't shown how you communicate with the device but the standard, more universal (but more work, since you'll have to redo your comms code) approach is to use asynchronous, non-blocking i/o. That's a fairly big python topic for which you can find lots of documentation - python version matters too. – pvg Mar 07 '17 at 19:18
  • Actually the commanding and testing of the devices is relatively asynch. It only takes a couple seconds to issue and command and get confirmation that the device is responding. I'm using 2.7.10 and then to communicate pyserial and the normal serial connections there. And then I have these loops that wait 10-30 seconds and ping the serial port to see if that set of commands has been finished. So I suppose I could test in there if a pause has been issued. That still doesn't seem very clean. – clifgray Mar 07 '17 at 19:24
  • Yeah I missed the pyserial tag. If the comms themselves are short and quick, then it doesn't really matter much. If you're waiting and polling, the interrupts should be a part of your wait-and-poll loop. That's another thing you can just parametrize and factor out so you don't have to write it 50 times. – pvg Mar 07 '17 at 19:27
  • Okay that all makes sense. Now sort of one of the issues I was fumbling with when I was initially thinking about this. While I'm waiting and polling I'm running through a time.sleep(30) or some similar time and the GUI freezes up during that. How can I make it wait and not freeze up the GUI so it can actually accept a pause event? – clifgray Mar 07 '17 at 21:54
  • That's a different question and there are several several answers on SO you can search your way to. Typically, you'd use tkinter's `after` callback. – pvg Mar 07 '17 at 22:28
  • Thanks for the help @pvg. For some more context if other folks stumble on this here is how I solved this and a larger issue: http://stackoverflow.com/questions/42912698/why-isnt-python-waiting-for-my-function-to-finish – clifgray Mar 21 '17 at 17:53
  • 1
    Sweet. Happy, uuh, bathing? – pvg Mar 21 '17 at 17:58

0 Answers0