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:
- Is a manager class the best way to do this, if not what is
- 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.