Communication Loop with a GUI
<p>I am making a GUI in Python for a machine. My python program runs on a Raspberry Pi 3 and it is communicating by serial with an Arduino UNO.</p>

<p>I've made a protocol; the arduino sends a "1". The raspberry knows the "1" stands for Run/Stop, and gives back the value of the RunStop variable (which is either 0 or 1). After that the arduino sends a "2". The raspberry knows it stands for the Speed value, and sends back the Speed variable (Which is between 1 and 4).</p>

<p>I've made the 2 following programs that work, but I don't know how to implement it in the GUI. Because when I add it before the <strong>root.mainloop()</strong> , the GUI won't start. And when I put it after, the while loop never starts.</p>

<p>Arduino Code:</p>

<pre><code>int RunStop, Speed = 0;

void setup() {

  Serial.begin(9600);
  pinMode(13, OUTPUT);
  pinMode(12, OUTPUT);
}


void loop() {
  Serial.write(1);
  delay(2000);
  RunStop = Serial.read(); 
  if (RunStop == 1) {
    digitalWrite(13, HIGH);
  } else {
    digitalWrite(13, LOW);
  }
  delay(2000);


  Serial.write(2);
  delay(2000);
  Speed = Serial.read(); 
  if (Speed == 3) {
    digitalWrite(12, HIGH);
  } else {
    digitalWrite(12, LOW);
  }
  delay(2000);
}
</code></pre>

<p>Python Communication Code:</p>

<pre><code>import time
import serial

# configure the serial connections (the parameters differs on the device you are connecting to)
ser = serial.Serial(
    port='/dev/ttyAMA0',
    baudrate=9600,
)

ser.isOpen()

##Status variabelen

RunStop = b'\x01'
Speed = b'\x03'

while 1:
uartIn = ser.read()
print (uartIn)
time.sleep(1)
if uartIn == b'\x01':
    ser.write(RunStop)
    print ("Run/Stop Register")
elif uartIn == b'\x02':
    ser.write(Speed)
    print ("Speed Register")
else:
    print ("No Register Requested")
</code></pre>

<p>GUI code:</p>

<pre><code>from tkinter import *
import time
import serial

# configure the serial connections (the parameters differs on the device you are connecting to)
ser = serial.Serial(
    port='/dev/ttyAMA0',
    baudrate=9600,
    parity=serial.PARITY_ODD,
    stopbits=serial.STOPBITS_ONE,
    bytesize=serial.EIGHTBITS
)

ser.isOpen()

root = Tk()
root.title("Cutter")

## Layout







label_1 = Label(root, text="Pieces")
label_2 = Label(root, text="Length")
entry_1 = Entry(root, width=9)

entry_2 = Entry(root, width=9)


label_1.grid(row=3, sticky=W, padx=(10))
label_2.grid(row=4, sticky=W, padx=(10))
entry_1.grid(row=3, column=1, pady=(10,10))
entry_2.grid(row=4, column=1, pady=(10,15))


runStop = b'\x00'
Speed = b'\x01'
Pieces = b'\x00'
Length = b'\x00'


##pieces OK button function
def piecesOKphase():
    x = entry_1.get()
    print("pieces: " + x)

##length OK button function
def lengthOKphase():
    x = entry_2.get()
    print("length: " + x)

def runPhase():
    global runStop
    runStop = b'\x01'

def stopPhase():
    global runStop
    runStop = b'\x00'





#OK and RUN / STOP buttons
piecesOK = Button(root, text="OK", command=piecesOKphase)
piecesOK.grid(row=3, column=2)

lengthOK = Button(root, text="OK", command=lengthOKphase)
lengthOK.grid(row=4, column=2)

runButton = Button(root, text="Run", width=6, bg='#58FA58', command=runPhase)
stopButton = Button(root, text="Stop", width=6, bg='#FA5858', command=stopPhase)

runButton.grid(row=0, column=0,padx=(10,10), pady=(10,10))
stopButton.grid(row=0, column=2)



##speed container

CurrentSpeed = 1




def delSpeed():
    global CurrentSpeed
    global speedLabel

    if CurrentSpeed &gt; 1:
        CurrentSpeed -= 1
        speedLabel.config(text=str(CurrentSpeed))
        speedLabel.config(text=CurrentSpeed)

    else:
        pass

def addSpeed():
    global CurrentSpeed

    if CurrentSpeed &lt; 4:
        CurrentSpeed += 1
        speedLabel.config(text=CurrentSpeed)

    else:
        pass


speedMin = Button(root, text="Speed -", width=6, command=delSpeed)

speedLabel = Label(root, text=CurrentSpeed, font=20)
speedLabel.grid(row=1, column=1, padx=(10))

speedPlus = Button(root, text="Speed +", width=6, command=addSpeed)

speedMin.grid(row=1, column=0)
speedPlus.grid(row=1, column=2)


#Number keyboard functions

def B1phase():
    try:
        root.focus_get().insert(END, "1")
    except AttributeError:
        pass
def B2phase():
    try:
        root.focus_get().insert(END, "2")
    except AttributeError:
        pass
def B3phase():
    try:
        root.focus_get().insert(END, "3")
    except AttributeError:
        pass
def B4phase():
    try:
        root.focus_get().insert(END, "4")
    except AttributeError:
        pass
def B5phase():
    try:
        root.focus_get().insert(END, "5")
    except AttributeError:
        pass
def B6phase():
    try:
        root.focus_get().insert(END, "6")
    except AttributeError:
        pass
def B7phase():
    try:
        root.focus_get().insert(END, "7")
    except AttributeError:
        pass
def B8phase():
    try:
        root.focus_get().insert(END, "8")
    except AttributeError:
        pass
def B9phase():
    try:
        root.focus_get().insert(END, "9")
    except AttributeError:
        pass
def B0phase():
    try:
        root.focus_get().insert(END, "0")
    except AttributeError:
        pass
def clearPhase():
    try:
        root.focus_get().delete(0, 'end')
    except AttributeError:
        pass


## Number keyboard buttons
B1 = Button(root, text="1", width=6, command=B1phase)
B2 = Button(root, text="2", width=6, command=B2phase)
B3 = Button(root, text="3", width=6, command=B3phase)
B4 = Button(root, text="4", width=6, command=B4phase)
B5 = Button(root, text="5", width=6, command=B5phase)
B6 = Button(root, text="6", width=6, command=B6phase)
B7 = Button(root, text="7", width=6, command=B7phase)
B8 = Button(root, text="8", width=6, command=B8phase)
B9 = Button(root, text="9", width=6, command=B9phase)
B0 = Button(root, text="0", width=6, command=B0phase)
clearButton = Button(root, text="Clear", width=6, bg='#FA5858', command=clearPhase)


B1.grid(row=5, column=0)
B2.grid(row=5, column=1)
B3.grid(row=5, column=2, padx=(10,10))
B4.grid(row=6, column=0)
B5.grid(row=6, column=1)
B6.grid(row=6, column=2, padx=(10,10))
B7.grid(row=7, column=0)
B8.grid(row=7, column=1)
B9.grid(row=7, column=2, padx=(10,10))
B0.grid(row=8, column=1)
clearButton.grid(row=8, column=0)



## Manual

label_4 = Label(root, text="")
label_4.grid(row=2, sticky=W)
label_3 = Label(root, text="Manual")
label_3.grid(row=9, sticky=W, padx=(10), pady=(10,10))

manualCut = Button(root, text="Cut", width=4)
manualCut.grid(row=10, column=0, pady=(1,15))

manualFeed = Button(root, text="Feed", width=4)
manualFeed.grid(row=10, column=1, pady=(1,15))


# Test function
def testPhase():

    print (CurrentSpeed)

manualTest = Button(root, text="Test", width=4, command=testPhase)
manualTest.grid(row=10, column=2, pady=(1,15))




## OTHER


root.mainloop()
</code></pre>

<p><strong>EDIT</strong></p>

<p>I have used the <strong>after</strong> from Tkinter.</p>

<p>The GUI starts, and the while loop runs. But I cannot click on any buttons.</p>

<pre><code>from tkinter import *
import time
import serial

# configure the serial connections (the parameters differs on the device you are connecting to)
ser = serial.Serial(
    port='/dev/ttyAMA0',
    baudrate=9600,
    parity=serial.PARITY_ODD,
    stopbits=serial.STOPBITS_ONE,
    bytesize=serial.EIGHTBITS
)
ser.isOpen()





## GUI Title
root = Tk()
root.title("Cutter")





##Communication
RunStop = b'\x01' ## 0 = OFF, 1 = ON

Speed = b'\x03' ## 1, 2, 3, 4
CurrentSpeed = 1

Pieces = b'\x00'

Pieces100 = b'\x00'
Pieces10 = b'\x00'
Pieces1 = b'\x00'

length10000 = b'\x00'
Length1000 = b'\x00'
Length100 = b'\x00'
Length10 = b'\x00'
Length1 = b'\x00'



def Communication():
    while 1:
        uartIn = ser.read()
        print (uartIn)
        time.sleep(1)
        if uartIn == b'\x01':
            ser.write(RunStop)
            print ("Run/Stop Register")
        elif uartIn == b'\x02':
            ser.write(Speed)
            print ("Speed Register")
        else:
            print ("No Register Requested")
        root.after(500, Communication)





## Functions
def runBOT():
    global RunStop
    RunStop = b'\x01'

def stopBOT():
    global RunStop
    RunStop = b'\x00'

def delSpeedBOT():
    global CurrentSpeed
    global speedLabel

    if CurrentSpeed &gt; 1:
        CurrentSpeed -= 1
        speedLabel.config(text=str(CurrentSpeed))
        speedLabel.config(text=CurrentSpeed)
    else:
        pass

def addSpeedBOT():
    global CurrentSpeed

    if CurrentSpeed &lt; 4:
        CurrentSpeed += 1
        speedLabel.config(text=CurrentSpeed)
    else:
        pass

def piecesBOT():
    x = entryPieces.get()
    print("pieces: " + x)

def lengthBOT():
    x = entryLength.get()
    print("length: " + x)





## Layout

labelPieces = Label(root, text="Pieces")
labelLength = Label(root, text="Length")
entryPieces = Entry(root, width=9)
entryLength = Entry(root, width=9)

labelPieces.grid(row=3, sticky=W, padx=(10))
labelLength.grid(row=4, sticky=W, padx=(10))
entryPieces.grid(row=3, column=1, pady=(10,10))
entryLength.grid(row=4, column=1, pady=(10,15))





## Buttons

runButton = Button(root, text="Run", width=6, bg='#58FA58', command=runBOT)
runButton.grid(row=0, column=0,padx=(10,10), pady=(10,10))

stopButton = Button(root, text="Stop", width=6, bg='#FA5858', command=stopBOT)
stopButton.grid(row=0, column=2)

speedMin = Button(root, text="Speed -", width=6, command=delSpeedBOT)
speedMin.grid(row=1, column=0)

speedLabel = Label(root, text=CurrentSpeed, font=20)
speedLabel.grid(row=1, column=1, padx=(10))

speedPlus = Button(root, text="Speed +", width=6, command=addSpeedBOT)
speedPlus.grid(row=1, column=2)

piecesOK = Button(root, text="OK", command=piecesBOT)
piecesOK.grid(row=3, column=2)

lengthOK = Button(root, text="OK", command=lengthBOT)
lengthOK.grid(row=4, column=2)





#Number keyboard functions

def B1phase():
    try:
        root.focus_get().insert(END, "1")
    except AttributeError:
        pass
def B2phase():
    try:
        root.focus_get().insert(END, "2")
    except AttributeError:
        pass
def B3phase():
    try:
        root.focus_get().insert(END, "3")
    except AttributeError:
        pass
def B4phase():
    try:
        root.focus_get().insert(END, "4")
    except AttributeError:
        pass
def B5phase():
    try:
        root.focus_get().insert(END, "5")
    except AttributeError:
        pass
def B6phase():
    try:
        root.focus_get().insert(END, "6")
    except AttributeError:
        pass
def B7phase():
    try:
        root.focus_get().insert(END, "7")
    except AttributeError:
        pass
def B8phase():
    try:
        root.focus_get().insert(END, "8")
    except AttributeError:
        pass
def B9phase():
    try:
        root.focus_get().insert(END, "9")
    except AttributeError:
        pass
def B0phase():
    try:
        root.focus_get().insert(END, "0")
    except AttributeError:
        pass
def clearPhase():
    try:
        root.focus_get().delete(0, 'end')
    except AttributeError:
        pass





## Number keyboard buttons
B1 = Button(root, text="1", width=6, command=B1phase)
B2 = Button(root, text="2", width=6, command=B2phase)
B3 = Button(root, text="3", width=6, command=B3phase)
B4 = Button(root, text="4", width=6, command=B4phase)
B5 = Button(root, text="5", width=6, command=B5phase)
B6 = Button(root, text="6", width=6, command=B6phase)
B7 = Button(root, text="7", width=6, command=B7phase)
B8 = Button(root, text="8", width=6, command=B8phase)
B9 = Button(root, text="9", width=6, command=B9phase)
B0 = Button(root, text="0", width=6, command=B0phase)
clearButton = Button(root, text="Clear", width=6, bg='#FA5858', command=clearPhase)

B1.grid(row=5, column=0)
B2.grid(row=5, column=1)
B3.grid(row=5, column=2, padx=(10,10))
B4.grid(row=6, column=0)
B5.grid(row=6, column=1)
B6.grid(row=6, column=2, padx=(10,10))
B7.grid(row=7, column=0)
B8.grid(row=7, column=1)
B9.grid(row=7, column=2, padx=(10,10))
B0.grid(row=8, column=1)
clearButton.grid(row=8, column=0)





## Manual

label_4 = Label(root, text="")
label_4.grid(row=2, sticky=W)
label_3 = Label(root, text="Manual")
label_3.grid(row=9, sticky=W, padx=(10), pady=(10,10))

manualCut = Button(root, text="Cut", width=4)
manualCut.grid(row=10, column=0, pady=(1,15))

manualFeed = Button(root, text="Feed", width=4)
manualFeed.grid(row=10, column=1, pady=(1,15))


# Test function
def testPhase():
    pass

manualTest = Button(root, text="Test", width=4, command=testPhase)
manualTest.grid(row=10, column=2, pady=(1,15))





## Running GUI and communication
root.after(500, Communication)


root.mainloop()
</code></pre>

<p>This doesn't work:
    Communication()
    root.mainloop()</p>

<p><strong>Assuming you can do a non-blocking read on the serial port</strong>, you can write a function that reads the serial port, and then schedules itself to be called again after a delay. </p>

<p>For example:</p>

<pre><code>def poll_serial(root):

    uartIn = ser.read()
    if uartIn:
        process_value_from_serial(uartIn)

    root.after(1000, poll_serial, root)
</code></pre>

<p>In your main program, you call this immediately before starting <code>mainloop</code> and it will run every second for the life of the program.</p>

<pre><code>root = Tk()
...
poll_serial(root)
root.mainloop()
</code></pre>

<p><strong>If calling <code>ser.read()</code> blocks</strong>, you can modify the polling function to look like this<sup>1</sup>:</p>

<pre><code>def poll_serial(root):
    if (ser.inWaiting()&gt;0):
        uartIn = ser.read()
        if uartIn:
            process_value_from_serial(uartIn)
    root.after(1000, poll_serial, root)
</code></pre>

<p><sup>1</sup> Code for checking the serial port before reading came from this answer: <a href="http://stackoverflow.com/a/38758773/7432">http://stackoverflow.com/a/38758773/7432</a></p>

python tkinter arduino pyserial raspberry-pi3