0

I am working on a project that will have a tkinter GUI running in one process, and a second process that is reading data from an Arduino over pyserial. These processes are defined using the class based method, as seen in this stackoverflow post.

I would like to be able to have the tkinter class send messages that will be sent over the serial line, and for the tkinter class to be able to read the data from the Arduino. I understand that a queue would be best if I was reading all of the data as it came in (such as sending information to the arduino), but for getting data to display on the tkinter GUI, I want to only periodically poll the most recent line that was received from the arduino. From what I can tell, the best way to share a variable between processes would be to use a manager, but all of the examples using a manager do not use class-based processes.

The only example that I can find is this stackexchange post. However, the sample that he has written is not complete, and I cannot seem to figure out what else needs to be added to the code in order for the method to work.

I suppose that my question is twofold:

  1. Is a manager class the best way to do this, if not what is
  2. How exactly do I get the data to be shared, as all of my attempts do not seem to share data between the processes.

Here is the code that I have written (without a manager class)

import tkinter as tk
import multiprocessing
import serial
import time
from multiprocessing.managers import BaseManager


# pylint: disable=C0111

accData = [1, 2, 3]
gyroData = [4, 5, 6]
magData = [7, 8, 9]
altitude = 10
temp = 11
pres = 12


class SerCotrol(multiprocessing.Process):

    def run(self):
        ser = serial.Serial('COM4', 9600)
        while not self.exit.is_set():
            s = ser.write(b'h')
            s = ser.read(10)
            s = s.decode("utf-8")
            x = s.split('\t')
            for i, num in enumerate(x):
                try:
                    x[i] = int(float(num))
                except ValueError:
                    pass

        ser.close()

    def __init__(self, shared):
        self.exit = multiprocessing.Event()
        multiprocessing.Process.__init__(self)
        self.shared = shared

    def shutdown(self):
        print("Shutdown initiated")
        self.exit.set()


class GUI():

    def __init__(self, master):

        master.title("A simple GUI")

        self.num = 0
        self.labelNum = tk.IntVar()
        self.labelNum.set(self.num)
        self.label = tk.Label(master, textvariable=self.labelNum)
        self.accString = tk.StringVar()
        self.accString.set('Accleration\nX: %.5f\nY: %.5f\nZ: %.5f' %
                           (accData[0], accData[1], accData[2]))
        self.gyroString = tk.StringVar()
        self.gyroString.set('Gyros\nX: %.5f\nY: %.5f\nZ: %.5f' %
                            (gyroData[0], gyroData[1], gyroData[2]))
        self.magString = tk.StringVar()
        self.magString.set('Magnetometer\nX: %.5f\nY: %.5f\nZ: %.5f' % (
            magData[0], magData[1], magData[2]))
        self.presString = tk.StringVar()
        self.presString.set('Pressure: \n%.5f' % (pres))
        self.tempString = tk.StringVar()
        self.tempString.set('Tempature: \n%.5f' % (temp))
        self.altString = tk.StringVar()
        self.altString.set('Alititude: \n%.5f' % (altitude))

        self.groundMode = tk.Button(
            master, text='GROUND\nREADY', height=10, width=20)
        self.launch = tk.Button(master, text='BOOST', width=20, height=10)
        self.stabilizeFlight = tk.Button(
            master, text='SEPARATE', height=10, width=20)
        self.targetAq = tk.Button(
            master, text='STABILIZE', height=10, width=20)
        self.recovery = tk.Button(master, text='SEARCH', height=10, width=20)
        self.flightComplete = tk.Button(
            master, text='ORBIT', height=10, width=20)
        self.landed = tk.Button(master, text='LANDED', height=10, width=20)
        self.abort_button = tk.Button(
            master, text="ABORT!", command=self.abort, height=10, background='red')
        self.close_button = tk.Button(master, text="Close", command=self.quit)
        self.accDisp = tk.Label(
            master, textvariable=self.accString, font=("TkDefaultFont", 10))
        self.gyroDisp = tk.Label(
            master, textvariable=self.gyroString, font=("TkDefaultFont", 10))
        self.magDisp = tk.Label(
            master, textvariable=self.magString, font=("TkDefaultFont", 10))
        self.presDisp = tk.Label(
            master, textvariable=self.presString, font=("TkDefaultFont", 10))
        self.tempDisp = tk.Label(
            master, textvariable=self.tempString, font=("TkDefaultFont", 10))
        self.altDisp = tk.Label(
            master, textvariable=self.altString, font=("TkDefaultFont", 10))
        self.startCom = tk.Button(
            master, text="Start Communication", command=self.startComProcess)

        self.label.grid(row=0, sticky=(tk.E, tk.W))
        self.groundMode.grid(row=1, sticky=(tk.E, tk.W, tk.S))
        self.launch.grid(row=1, column=1, sticky=(tk.E, tk.W, tk.S))
        self.stabilizeFlight.grid(row=1, column=2, sticky=(tk.E, tk.W, tk.S))
        self.targetAq.grid(row=1, column=3, sticky=(tk.E, tk.W, tk.S))
        self.recovery.grid(row=1, column=4,  sticky=(tk.E, tk.W, tk.S))
        self.flightComplete.grid(row=1, column=5,  sticky=(tk.E, tk.W, tk.S))
        self.landed.grid(row=1, column=6, sticky=(tk.E, tk.W, tk.S))
        self.abort_button.grid(row=2, columnspan=7, sticky=(tk.E, tk.W, tk.N))
        self.close_button.grid(row=3, columnspan=7)
        self.startCom.grid(row=4, columnspan=7)
        self.accDisp.grid(row=5, column=0)
        self.gyroDisp.grid(row=5, column=1)
        self.magDisp.grid(row=5, column=2)
        self.presDisp.grid(row=5, column=3)
        self.tempDisp.grid(row=5, column=4)
        self.altDisp.grid(row=6, column=3)

    @classmethod
    def quit(self):
        process.shutdown()
        print(process.is_alive())
        root.quit()

    @classmethod
    def abort(self):
        print(SharedData().get_value())
        print("ABORTING!")

    @classmethod
    def startComProcess(self):
        process.start()

    def getData(self):
        pass

if __name__ == "__main__":
    # ShareManager.register(BaseManager)
    process = SerCotrol()
    root = tk.Tk()
    root.rowconfigure((1), weight=1, uniform='test')
    root.columnconfigure((0, 1, 2, 3, 4, 5, 6), weight=1)
    GUI = GUI(root)
    root.mainloop()

And here is the same code, but with my most recent attempt to use the manager class:

import tkinter as tk
import multiprocessing
import serial
import time
from multiprocessing.managers import BaseManager


# pylint: disable=C0111

accData = [1, 2, 3]
gyroData = [4, 5, 6]
magData = [7, 8, 9]
altitude = 10
temp = 11
pres = 12


class SharedData():

    def __init__(self):
        self._data = []

    def update(self, value):
        self._data = value

    def get_value(self):
        return self._data

class SerCotrol(multiprocessing.Process):

    def run(self):
        ser = serial.Serial('COM4', 9600)
        while not self.exit.is_set():
            s = ser.write(b'h')
            s = ser.read(10)
            s = s.decode("utf-8")
            x = s.split('\t')
            for i, num in enumerate(x):
                try:
                    x[i] = int(float(num))
                except ValueError:
                    pass
            SharedData().update(x)
            # print(manager)

        ser.close()

    def __init__(self, shared):
        self.exit = multiprocessing.Event()
        multiprocessing.Process.__init__(self)
        self.shared = shared

    def shutdown(self):
        print("Shutdown initiated")
        self.exit.set()


class GUI():

    def __init__(self, master):

        master.title("A simple GUI")

        self.num = 0
        self.labelNum = tk.IntVar()
        self.labelNum.set(self.num)
        self.label = tk.Label(master, textvariable=self.labelNum)
        self.accString = tk.StringVar()
        self.accString.set('Accleration\nX: %.5f\nY: %.5f\nZ: %.5f' %
                           (accData[0], accData[1], accData[2]))
        self.gyroString = tk.StringVar()
        self.gyroString.set('Gyros\nX: %.5f\nY: %.5f\nZ: %.5f' %
                            (gyroData[0], gyroData[1], gyroData[2]))
        self.magString = tk.StringVar()
        self.magString.set('Magnetometer\nX: %.5f\nY: %.5f\nZ: %.5f' % (
            magData[0], magData[1], magData[2]))
        self.presString = tk.StringVar()
        self.presString.set('Pressure: \n%.5f' % (pres))
        self.tempString = tk.StringVar()
        self.tempString.set('Tempature: \n%.5f' % (temp))
        self.altString = tk.StringVar()
        self.altString.set('Alititude: \n%.5f' % (altitude))

        self.groundMode = tk.Button(
            master, text='GROUND\nREADY', height=10, width=20)
        self.launch = tk.Button(master, text='BOOST', width=20, height=10)
        self.stabilizeFlight = tk.Button(
            master, text='SEPARATE', height=10, width=20)
        self.targetAq = tk.Button(
            master, text='STABILIZE', height=10, width=20)
        self.recovery = tk.Button(master, text='SEARCH', height=10, width=20)
        self.flightComplete = tk.Button(
            master, text='ORBIT', height=10, width=20)
        self.landed = tk.Button(master, text='LANDED', height=10, width=20)
        self.abort_button = tk.Button(
            master, text="ABORT!", command=self.abort, height=10, background='red')
        self.close_button = tk.Button(master, text="Close", command=self.quit)
        self.accDisp = tk.Label(
            master, textvariable=self.accString, font=("TkDefaultFont", 10))
        self.gyroDisp = tk.Label(
            master, textvariable=self.gyroString, font=("TkDefaultFont", 10))
        self.magDisp = tk.Label(
            master, textvariable=self.magString, font=("TkDefaultFont", 10))
        self.presDisp = tk.Label(
            master, textvariable=self.presString, font=("TkDefaultFont", 10))
        self.tempDisp = tk.Label(
            master, textvariable=self.tempString, font=("TkDefaultFont", 10))
        self.altDisp = tk.Label(
            master, textvariable=self.altString, font=("TkDefaultFont", 10))
        self.startCom = tk.Button(
            master, text="Start Communication", command=self.startComProcess)

        self.label.grid(row=0, sticky=(tk.E, tk.W))
        self.groundMode.grid(row=1, sticky=(tk.E, tk.W, tk.S))
        self.launch.grid(row=1, column=1, sticky=(tk.E, tk.W, tk.S))
        self.stabilizeFlight.grid(row=1, column=2, sticky=(tk.E, tk.W, tk.S))
        self.targetAq.grid(row=1, column=3, sticky=(tk.E, tk.W, tk.S))
        self.recovery.grid(row=1, column=4,  sticky=(tk.E, tk.W, tk.S))
        self.flightComplete.grid(row=1, column=5,  sticky=(tk.E, tk.W, tk.S))
        self.landed.grid(row=1, column=6, sticky=(tk.E, tk.W, tk.S))
        self.abort_button.grid(row=2, columnspan=7, sticky=(tk.E, tk.W, tk.N))
        self.close_button.grid(row=3, columnspan=7)
        self.startCom.grid(row=4, columnspan=7)
        self.accDisp.grid(row=5, column=0)
        self.gyroDisp.grid(row=5, column=1)
        self.magDisp.grid(row=5, column=2)
        self.presDisp.grid(row=5, column=3)
        self.tempDisp.grid(row=5, column=4)
        self.altDisp.grid(row=6, column=3)

    @classmethod
    def quit(self):
        process.shutdown()
        print(process.is_alive())
        root.quit()

    @classmethod
    def abort(self):
        print(SharedData().get_value())
        print("ABORTING!")

    @classmethod
    def startComProcess(self):
        process.start()

    def getData(self):
        pass


class ShareManager(BaseManager):
    pass


ShareManager.register('SharedData', BaseManager)

if __name__ == "__main__":
    # ShareManager.register(BaseManager)
    manager = ShareManager()
    manager.start()
    shared = manager.SharedData()
    process = SerCotrol(shared)
    root = tk.Tk()
    root.rowconfigure((1), weight=1, uniform='test')
    root.columnconfigure((0, 1, 2, 3, 4, 5, 6), weight=1)
    GUI = GUI(root)
    root.mainloop()

Please not that the GUI is mostly non-functional at the point. My goal was to get the gui to simply print the data to the command line first, and then I would add the ability to send the data to the labels in the GUI.

Please let me know if there is any additional information needed, and I will be happy to provide it.

gre_gor
  • 6,669
  • 9
  • 47
  • 52
BarrowWight
  • 181
  • 2
  • 12
  • Or you could Keep It Simple and just use the tkinter `.after()` method to periodically read from the serial port. No need for multiprocessing or threading with that method. – scotty3785 Jul 27 '18 at 11:43
  • That would run every time tkinter is updated, right? I was hoping to update the graphical display at a different rate, so that the incoming values are legible. For example, only update the numbers on the display ever .1 seconds, even if pyserial reads every .01 seconds. I imagine this would also increase the rate that I read from the serial port, and would allow me to more eaisly also save the data from the serial port to a .csv file. Does that make any sense, or am I being a bit confusing/stupid? Either way, thank you for that thought, I will consider that method as well. – BarrowWight Jul 27 '18 at 14:45
  • You can do the different rates if you want. Have one function called using .after which reads from the serial port and stores the results and another, at a different rate, which updates the screen. – scotty3785 Jul 30 '18 at 10:04
  • @scotty3785 each after runs a different thread? – BarrowWight Jul 30 '18 at 14:38
  • Not a separate thread, just scheduled to occur at different rates. People have been doing this for years on microcontrollers where there is no OS to deal with the threads. – scotty3785 Jul 30 '18 at 15:00
  • Iteresting. I might have to try that out. For now I am using your furst suggestion. – BarrowWight Jul 30 '18 at 15:02
  • My answer to another question might help. https://stackoverflow.com/questions/49660594/how-to-read-serial-data-with-multiprocessing-in-python/49890421#49890421 – David Mayes Jul 30 '18 at 22:58

0 Answers0