Indigo Indigo - 9 days ago 6
Python Question

Tkinter not saving values between keypresses?

At least I think thats what's going on...

I am running two motors over a serial connection. The control values for Motor R range from 1 - 127, Motor L ranges from 128 to 255.

I am using tkinter to capture arrow key presses, I then interpret that to feed to the motors, this works fine except I want to save the current state or the motor so I can know if I need to change the motor at the next keypress.

It used to work until I added multi threading.

This is the code that's trying to save state:

def save_cur_state(save_state):
print'save_state'
print save_state
print'okay'
if (save_state > 0 and save_state < 128):
last_goodR(save_state)
elif (save_state < 256 and save_state > 127):
last_goodL(save_state)
else:
print'wtf?'

def last_goodR(n):
global cur_stateR_save
cur_stateR_save = n
print 'RRRRRR'
print cur_stateR

def last_goodL(n):
global cur_stateL_save
cur_stateL_save = n
print'LLLL'
print cur_stateL

def get_cur_state():
global cur_stateR_save
global cur_stateL_save
print 'accessing saves'
print cur_stateR_save
print cur_stateL_save

cur_stateR = cur_stateR_save
cur_stateL = cur_stateL_save
return(cur_stateL, cur_stateR)


Everything seems to run fine, my prints all give what they ought to.
But then, when I press the key again, the "saved" values all default back to zero and I have no idea why. I've tried a billion different ways to work around it but I just can't get the values to stick. Does anyone know why this might be the case or how I should work around it?
(My entire code is below in case you'd like to see it)
Thank you!!

import rospy
from std_msgs.msg import Int64
from Tkinter import *
import time
from serial import Serial
from multiprocessing import Process


#Defines
M1B = 1
M1S = 64
M1F = 127
M2B = 128
M2S = 192
M2F = 255

S = 0
F = 1
B = 2
L = 3
R = 4


#serialPort = Serial('/dev/ttyAMA0', 9600, timeout = 2)
cur_stateR = M1S
cur_stateL = M2S
next_stateR = M1S
next_state = M2S

main = Tk()


def kp(event):
if event.keysym == 'Up' :
direction_set(F)
elif event.keysym =='Down' :
direction_set(B)
elif event.keysym =='Left' :
direction_set(L)
elif event.keysym =='Right' :
direction_set(R)
else :
print'fooo'
try:
print'try'
cur_stateR
cur_stateL
except NameError:
print'except'

cur_stateR = 0
cur_stateL = 0
last_goodR(0)
last_goodL(0)


def direction_set(direction):
if direction == F:
next_stateR = M1F
next_stateL = M2F
elif direction == B:
next_stateR = M1B
next_stateL = M2B
elif direction == R:
next_stateR = M1B
next_stateL = M2F
elif direction == L:
next_stateR = M1F
next_stateL = M2B
elif direction == S:
next_stateR = M1S
next_stateL = M2S
else:
next_stateR = M1S
next_stateL = M2S
update(next_stateR, next_stateL)

def update(next_stateR, next_stateL):
if (cur_stateR != next_stateR):
p1 = Process(target = motor1, args = (next_stateR,))
p1.start()

if (cur_stateL != next_stateL):
p2 = Process(target = motor2, args = (next_stateL,))
p2.start()


def motor1(next_stateR):
#change the R motor
print'start motor1'
cur_stateR, cur_stateL = get_cur_state()
if (cur_stateR != next_stateR):
#first set to 64
cur_stateR = M1S
refresh(cur_stateR)
time.sleep(1)
# then itterate to the desired stat
if next_stateR == M1F:
for x in range (M1S, M1F+1):
cur_stateR = x
time.sleep(.01)
refresh(cur_stateR)
elif next_stateR == M1B:
for x in range (M1S, M1B-1, -1):
cur_stateR = x
time.sleep(.01)
refresh(cur_stateR)

save_cur_state(cur_stateR)
print 'end motor1'

def motor2 (next_stateL):
#change the L motor
print 'start motor2'
cur_stateR, cur_stateL = get_cur_state()
if (cur_stateL != next_stateL):
#first set to 64
cur_stateL = M2S
refresh(cur_stateL)
time.sleep(1)
# then itterate to the desired stat
if next_stateL == M2F:
for x in range (M2S, M2F+1):
cur_stateL = x
time.sleep(.01)
refresh(cur_stateL)
elif next_stateL == M2B:
for x in range (M2S, M2B-1, -1):
cur_stateL = x
time.sleep(.01)
refresh(cur_stateL)
#print 'to save:'
#print cur_stateR
#print cur_stateL
save_cur_state(cur_stateL)

def refresh(update_state):
#serialPort.write(chr(update_state))
print update_state

def save_cur_state(save_state):
print'save_state'
print save_state
print'okay'
if (save_state > 0 and save_state < 128):
last_goodR(save_state)
elif (save_state < 256 and save_state > 127):
last_goodL(save_state)
else:
print'wtf?'
''' try:
print'try'
cur_stateR
cur_stateL
except NameError:
print'except'

cur_stateR = 0
cur_stateL = 0
last_goodR(0)
last_goodL(0)
else:'''


def last_goodR(n):
global cur_stateR_save
cur_stateR_save = n
print 'RRRRRR'
print cur_stateR

def last_goodL(n):
global cur_stateL_save
cur_stateL_save = n
print'LLLL'
print cur_stateL

def get_cur_state():
global cur_stateR_save
global cur_stateL_save
print 'saves acess'
print cur_stateR_save
print cur_stateL_save

cur_stateR = cur_stateR_save
cur_stateL = cur_stateL_save
return(cur_stateL, cur_stateR)


main.bind_all('<KeyPress>', kp)


main.mainloop()

serialPort.close()

Answer

First off, before you try to do more with "threading", you should know that tkinter isn't threadsafe; GUI elements should not be accessed from anything but the thread that created them, any cross-thread usage is going to be unreliable at best, and guaranteed wrong the rest of the time.

Secondly, you're not actually engaged in threading. You're using multiprocessing, which spawns new processes, and those processes do not not share memory (including stuff like global variables) with the process that spawned them except in very limited cases (e.g. multiprocessing.Array). On UNIX-like systems, they're using fork, and as soon as the Process begins running, it has a copy-on-write view of what the original process has; it can read the same data from the moment of the fork, but if it changes it, it only changes its own copy, not the parent process's. The set_cur_state call in motor1 is setting the child process's copy of the values, but the original process's copy is unmodified.

If you want to do work using threading (with threads, not processes), you can build on this simple example of how to do so.

If you're using multiprocessing, a similar approach could be taken using a multiprocessing.Queue. Alternatively, you could use a global multiprocessing.Pool, and instead of launching new Processes, you'd call apply_async on the pool when you need to dispatch work, change the function to accept state as arguments and return the new state, (instead of reading and writing global variables internally), and provide a callback function to apply_async that will be invoked with the return value. Note: From what I can tell, the callback will be invoked using a thread that communicates with the workers, not the main thread, so you still need to use the tricks from the example code to pass data back to widgets owned by the main thread.