Python tkinter buttons are not working during a -while loop- condition - python

i am doing some measurement with raspberry pi. I made a GUI with python tkinter. measurement is done every seconds. when the measured value is higher than a particular value, i want to set some pin high for 30 seconds (counter). but measurement should continue. and during this time, if there is another high value, again reset the counter to 30 seconds. So i used a while loop. I am displaying measured values in GUI. I am calling the main function (readSensor) every seconds using .after method. During while loop condition, GUI is frozen and value is not updated on screen and buttons are not working. Shall i remove while loop and use .after? but how to use the counter there?
status = False
def start():
global status
status = True
def stop():
global status
status = False
def readSensor():
# there is a start and stop button in GUI
if status:
measuredValues = []
pi.write(23,0) # pin 23 is low now
# code to do measurement is here
#it works fine
#result is a list with name measuredValues
#then checking the conditions below
if((measuredValues[0] > Limit_1) or (measuredValues[1] > Limit_2) or (measuredValues[2] > Limit_3) or (measuredValues[3] > Limit_4)):
counter = 0
print(measuredValues)
while (counter <=30):
# this while is the problematic part. if condition is true, i will set the pin 23 high.
# i would like to set the pin high for 30 seconds
# no any buttons seems working during this time. why?
# stop button is also not working.
pi.write(23,1)
time.sleep(1.0) #shall i avoid this sleep?
print(counter)
measuredValues = []
#measurement will continue here
#same code to do measurement as before
#output is the same list with name measuredValues
print(measuredValues)
# if any of the new measuring values is/are higher than limit, want to reset the counter to 30
# shall I use .after method here? and avoid while loop?
# how to use it with a condition like maiximum 30s
if ((measuredValues[0] > Limit_1) or (measuredValues[1] > Limit_2) or (measuredValues[2] > Limit_3) or (measuredValues[3] > Limit_4) ):
counter = 0
else:
counter +=1
else:
pi.write(23,0)
print(measuredValues)
win.after(1000, readSensor) # new measurement every second
win = Tk()
# code for GUI here
win.after(1000, readSensor)
win.mainloop()

You should consider placing your function in a thread. So your start button in your GUI should call the function using:
import threading
start_button = Button(YourFrame, command = lambda:threading.Thread(target = readSensor).start()) #YourFrame is the Frame widget where you placed the start button
That should make the UI responsive while doing the calculations inside your funtion.

Related

stopping a python program at certain time and resuming temporarily with button

I have a python program running a clock and thermometer (raspberry pi + fourletterphat), see program below. I change between showing time or temperature by clicking a single button (gpio16).
What I need help with:
I want to pause the program during night, 21:00 - 06:00, and clear the display because the light is annoying. With the current code I get the display to clear at time for night to start but it does not start again.
If, during above period, the button is clicked I want the program to run for 10 seconds and then stop/clear display. I simply have no idea how to do this, Not even where start.
Is there an elegant way to do this, preferably by just adding something to the existing program. See below.
I have tried various ways of either clearing the display during night time and/or pausing the program until button is hit (but only during the night time, i want the program running during day to show temperature or time).
I have found many versions on finding it time.now is within my range for night but they seem not to be compatible with starting the program as described in point 2 above. (e.g. if time.now < night_end or time.now >= night_start:)
in code below function bright() sets the brightness AND turns off the display at night start 20:00.
Function night() is my feeble start on the restart display at night time but I have not gotten further.
#! /usr/bin/env python3
#Tempclock working with fourletterphat from pimoroni. One switch to change between temp and clock.
#sets brightness at startup but not continuously.
import glob
import time
import datetime
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(16, GPIO.IN, pull_up_down=GPIO.PUD_DOWN )
input_state = GPIO.input(16)
import fourletterphat as flp
# Find 1s temp sensor
base_dir = '/sys/bus/w1/devices/'
device_folder = glob.glob(base_dir + '28*')[0]
device_file = device_folder + '/w1_slave'
# times for setting brightness of display:
# d2 and d3 time dim and brighten display
d2 = 18
d3 = 7
# d4 and d5 time to turn off display for nigh
d4 = 20
d5 = 6
butt = 1
# Set brightness
def bright():
todays_date = datetime.datetime.now()
hn = (todays_date.hour)
if hn < d5 and hn > d4:
flp.clear()
flp.show()
elif hn < d2 and hn > d3:
flp.set_brightness(12)
else:
flp.set_brightness(0)
# Define nighttime display off
def night():
todays_date = datetime.datetime.now()
hn = (todays_date.hour)
if hn < d5 or hn => d4:
flp.set_brightness(5)
# define temp and time reading
def read_temp_raw():
f = open(device_file, 'r')
lines = f.readlines()
f.close()
return lines
def read_temp():
lines = read_temp_raw()
while lines[0].strip()[-3:] != 'YES':
time.sleep(0.2)
lines = read_temp_raw()
equals_pos = lines[1].find('t=')
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp_c = float(temp_string) / 1000.0
temp_f = temp_c * 9.0 / 5.0 + 32.0
return temp_c, temp_f
def display_temp():
# temp = read_temp()[1] # F
temp = read_temp()[0] # C
flp.print_float(temp, decimal_digits=1, justify_right=True)
flp.show()
def display_time():
now = datetime.datetime.now()
str_time = time.strftime("%H%M")
flp.print_number_str(str_time)
flp.show()
# Display time or temp button on gpio pin 16 push button counter "butt" and set
# brightness bright() according to time of day. Function night() turns on display for 10sec if at night, then turns it off.
while True:
bright()
if GPIO.input(16) == 0:
butt = butt + 1
night()
if butt > 2:
butt = 1
if butt == 1:
display_time()
flp.show()
elif butt == 2:
display_temp()
flp.show()
time.sleep(0.2)
I'll start by assuming that your code to display time and read temperature is working correctly. Also, since I don't have a RaspberryPi with me, I'm focusing on how to organise your logic on the parts that I think you are having difficulty. My code to specifically turn the display on or off may be wrong, but I guess you figured that part out already. Since your question is about an elegant way to do this, the solution is obviously opinion-based. What I am proposing here is a simple but sound way to do it.
The first thing to notice is that you want two modes of operation. I'll call them day_mode and night_mode. The behaviour you expect is quite different for each mode:
day_mode: display is always on, when button 16 is pressed, the display should change from temperature to time. It should be active from 6:00 until 21:00
night_mode: display is normally off. When button 16 is pressed, the display should turn on and display something (you didn't specify, but I'll assume it is the temperature). It should be active when day_mode is not active.
When thinking about modes, it usually helps to separate three things:
What you need to do when you enter that mode of operation (for example, when you enter day_mode you should turn on the display and show the clock, when you enter night_mode you should turn off the display)
What is the behaviour you want during the mode. In your case, it is what we discussed above.
Any clean-up action needed when you exit the mode. Here none is needed, so we will skip this part.
So, let's begin! From your original code, I'll keep everything but the functions night, bright and the final loop. The first thing to do is create a function that tells in which mode we should be:
import datetime as dt
def which_mode():
current_hour = dt.datetime.now().time().hour
# In your code you seemed to need special behaviour between 6 and 7 and 18 and 21,
# but since it was not in your question, I'm not including it here
if 6 <= current_hour <= 21:
return 'day_mode'
else:
return 'night_mode'
To represent the modes, I'll use functions. If your problem were more complex, classes with specific enter, exit and during methods and a full blown state machine implementation would be needed. Since your problem is small, let's use a small solution. Each mode is a function, and the fact that you are entering the mode is informed by a parameter is_entering. The function will also receive a signal indicating that the button was pressed (a rising edge, but change according to your implementation). Before that, we will create a function that displays time or temperature based on a parameter:
def display_what(what):
if what == 0: # Time
display_time()
else: # Temperature
display_temp()
display_info = 0 # 0 means time, 1 means temperature
def day_mode(is_entering=False, button_pressed=False):
global display_info # Not very elegant, but gets the job done
if is_entering:
# Here the display is turned on. I got the values from your `bright` function
flp.set_brightness(12)
flp.clear()
# This is the `during` logic. Note that it will be run right after `entering` the first time
if button_pressed:
display_info = (display_info + 1) % 2 # Cycles between 0 and 1
display_what(display_info) # Update the display accordingly
For the night_mode function, we will need to keep a global variable to record when the button was last pressed. By convention, when it is smaller than 0, it means we are not counting the time (so we do not keep "clearing the display" all the time).
time_of_last_press = -1.0
def night_mode(is_entering=False, button_pressed=False):
if is_entering:
# Setup the dark display
flp.clear()
flp.set_brightness(0)
if button_pressed:
# Record when the button was pressed and display something
time_of_last_press = time.time()
flp.set_brightness(4) # I understood from your code that at night the display is less bright
display_what(1) # Temperature? You can use a similar logic from day_mode to toogle the displays
elif ((time.time() - time_of_last_press) > 10.0) and (time_of_last_press >= 0):
flp.clear()
flp.set_brightness(0)
time_of_last_press = -1.0
Finally, the main loop becomes relatively simple: check what should be the next mode. If it is different from the current one, set is_entering as True. Then call the function for the current mode.
# Setup the GPIO to detect rising edges.
GPIO.add_event_detect(16, GPIO.RISING)
current_mode = None # In the first run, this forces the `entering` code of the correct mode to run
while True:
button_pressed = GPIO.event_detected(16)
next_mode = which_mode()
changed = next_mode != current_mode
current_mode = next_mode
if current_mode == 'day_mode':
day_mode(changed, button_pressed)
else: # There are only two modes, but could be an elif if there were more modes
night_mode(changed, button_pressed)
time.sleep(0.2)
You may have noticed that there is some repetition: for example, in night_mode, the code to clear the display is the same code used when entering it, so you could actually call night_mode(True) to run that code again. Most importantly, there are two ifs that are similar: the one in which_mode and the one in the main loop. This can be fixed (and the code made more generic) if we change which_mode to return the function representing the mode, instead of a string. This may be a little more difficult to read if you are not used to functions returning functions, but it is a very flexible way of doing this sort of state machine:
# Must be defined after the functions `day_mode` and `night_mode` are defined
def which_mode():
current_hour = dt.datetime.now().time().hour
# In your code you seemed to need special behaviour between 6 and 7 and 18 and 21,
# but since it was not in your question, I'm not including it here
if 6 <= current_hour <= 21:
return day_mode # Note that this is not a string. It is a function!
else:
return night_mode
# Setup the GPIO to detect rising edges.
GPIO.add_event_detect(16, GPIO.RISING)
current_mode = None # In the first run, this forces the `entering` code of the correct mode to run
while True:
button_pressed = GPIO.event_detected(16)
next_mode = which_mode()
changed = next_mode != current_mode
current_mode = next_mode
current_mode(changed, button_pressed) # Now current_mode is a reference to one of the two mode functions, so we just need to call it.
time.sleep(0.2)

python appjar function doesn't thread

having issues trying to get threading working in python using the awesome Appjar package.
The following program needs to count through a list, and update a progress bar simultaneously. I've followed the appjar documentation for threading, but it's returning NameError: name 'percent_complete' is not defined in the app.thread (line 35), in which you're meant to insert function params - my code is below:
from appJar import gui
import time
# define method the counts through a list of numbers, and updates the progress meter
def press(btn):
objects = [1,3,6]
total = len(objects)
current_object = 0
for i in objects:
print(i)
current_object += 1
current_percent_complete = (current_object / total) * 100
updateMeter(current_percent_complete)
time.sleep(1)
def updateMeter(percent_complete):
app.queueFunction(app.setMeter, "progress", percent_complete)
# create a GUI variable called app
app = gui("Login Window")
app.setBg("orange")
app.setFont(18)
# add GUI elements : a label, a meter, & a button
app.addLabel("title", "COUNTER")
app.setLabelBg("title", "blue")
app.setLabelFg("title", "orange")
app.addMeter("progress")
app.setMeterFill("progress", "green")
app.addButton("START COUNTING", press)
# put the updateMeter function in its own thread
app.thread(updateMeter, percent_complete)
# start the GUI
app.go()
I can get rid of the error by defining percent_complete like so:
from appJar import gui
import time
# define method the counts through a list of numbers, and updates the progress meter
percent_complete = 0
def press(btn):
...
However, when GUI loads and button is pressed it doesn't thread. Instead it iterates through the list, then updates the progress bar afterwards.
Has anyone come across the same issue? any insight would be awesomely appreciated!
Thanks!
There are a couple of issues here:
First, I'm not sure your maths result in good percentages to update the meter with, so you might not see much change - should you be using i?
Second, the GUI won't be updated until the loop (and the sleeps inside it) all complete. Instead, you should try counting how many items to process, and iterating through them with an after() function, see here: http://appjar.info/pythonLoopsAndSleeps/#conditional-loops
Third, the call to app.thread() at the end doesn't achieve much - it calls the update_meter() function with a parameter that doesn't exist, it can be removed.
Fourth, the actual update_meter() function isn't necessary, as you're not really using a thread - that can be removed as well...
Give this a try, once you've had a look at the maths:
current_object = 0
def press(btn):
global current_object
current_object = 0
processList()
def processList():
global current_object
objects = [1,3,6]
total = len(objects)
if current_object < total:
i = objects[current_object]
print(i)
current_object += 1
current_percent_complete = (current_object / total) * 100
app.setMeter("progress", current_percent_complete)
app.after(1000, processList)
UPDATE: just to clarify on the maths issue, you're dividing one integer by another: 0/3, 1/3, 2/3, 3/3 and so on. In python2 this will result in 0, in python3 you'll get fractions.

Pausing a loop in python

I have a loop that creates windows using PySide depending on the number the user enters
each window will have some calls for other functions.
I would like the second window opens after all the commands belongs to the first window is done.
So, Is there a way in Python to tell the loop to stop until a certain flag is True for example
Here's what I'm doing
for i in range(values):
self.CreatWindow() # the function that creates the window
def CreatWindow(self):
window = QtGui.QMainWindow(self)
window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
combo = QtGui.QComboBox(window)
combo.addItem(" ")
combo.addItem("60")
combo.addItem("45")
combo.activated[str].connect(self.onActivated)
btn = QtGui.QPushButton('OK', window)
btn.clicked.connect(self.ComputeVec)
window.show()
def onActivated(self, text):
angle = int(text)
def ComputeVec(self):
window.close()
getVecValue(angle)
Now in that function the window have some calls to other functions, and I want to set the flag to True in the last function getVecValue which will do some computations and store the result.
Instead of having a different loop to open new windows you could call CreatWindow in ComputeVec
and use a global variable count to maintain count of windows created before.
count = 0
def ComputeVec(self):
window.close()
getVecValue(angle)
global count
count += 1
if count in range(values) :
self.CreatWindow()
The loop does already behave like this, since the function call self.CreateWindow waits for the return value of the called function.
You can return an appropriate value from self.CreateWindow for example return True and do this:
for i in range(values):
success = self.CreateWindow()
if success:
continue
Anyway, if there is no return value in self.CreateWindow, the statement self.CreateWindow() is still evaluated and results to None. The loop is not finished until this result is achieved.

Pygame simple score system

I have created a simple score system for my pygame. but it's pausing the game. I know it's because of time.sleep but I don't how to sort it out.
The score system is to +100 every 5 seconds while start is true, code:
while start == True:
time.sleep(5)
score = score + 100
Full code with indentation: http://pastebin.com/QLd3YTdJ
code at line : 156-158
Thank you
Instead of using sleep, which stalls the game until time has elapsed, you want to count up an internal timer with the number of seconds which have passed. When you hit 5 seconds, increment the score and then reset the timer.
Something like this:
scoreIncrementTimer = 0
lastFrameTicks = pygame.time.get_ticks()
while start == True:
thisFrameTicks = pygame.time.get_ticks()
ticksSinceLastFrame = thisFrameTicks - lastFrameTicks
lastFrameTicks = thisFrameTicks
scoreIncrementTimer = scoreIncrementTimer + ticksSinceLastFrame
if scoreIncrementTimer > 5000:
score = score + 100
scoreIncrementTimer = 0
This could easily be improved (what if your frame rate is so low there's more than 5 seconds between frames?) but is the general idea. This is commonly called a "delta time" game timer implementation.
If i understand you correctly you dont want the while True: score += 100 loop to block your entire program?
You should solve it by moving the score adding to a seperate function
and use the intervalfunction of APScheduler http://packages.python.org/APScheduler/intervalschedule.html
from apscheduler.scheduler import Scheduler
# Start the scheduler
sched = Scheduler()
sched.start()
# Schedule job_function to be called every 5 seconds
#sched.interval_schedule(seconds=5)
def incr_score():
score += 100
This will result in APScheduler creating a thread for you running the function every 5 seconds.
you might need to do some changes to the function to make it work but it should get you started at least :).

Running a loop alongside a Tkinter application

I am a high school programming student and I have a small question. I have been tasked with writing a simple game in Tkinter where an icicle falls from the ceiling and you have to avoid it with your mouse. Simple enough. However, I have hit an issue. Whenever I run a loop in a Tkinter application, it won't open. I've tried with a for loop that pauses every .5 seconds using time.sleep() and the window opens as soon as the loop finishes. Is there some special thing I need to do to make loops work in Tkinter?
from Tkinter import *
import time
import random
class App:
def __init__(self, parent):
self.frame = Frame(root, bg= '#1987DF', width=800, height=800)
self.frame.bind("<Motion>", self.motionevent)
self.frame.pack()
#self.run()
def randhex(self):
b = "#"
for i in range(1, 7):
a = random.randint(0, 15)
if a == 10:
a = "A"
elif a == 11:
a = "B"
elif a == 12:
a = "C"
elif a == 13:
a = "D"
elif a == 14:
a = "E"
elif a == 15:
a = "F"
b = b+str(a)
return b
def motionevent(self, event):
xpos, ypos, bg = event.x, event.y, self.randhex()
str1 = "X : %d Y : %d BG : %s" % (xpos, ypos, bg)
root.title(str1)
x,y, delta = 100, 100, 10
self.frame.config(bg=bg)
def run(self):
for i in range(0, 10):
time.sleep(.5)
print 'i'
self.frame.config(bg=self.randhex())
root = Tk()
app = App(root)
root.mainloop()
Currently all it is supposed to do is change the background when the mouse moves. When the line in init that says self.run() is uncommented it will print 'i' 10 times then the window will open. Help?
Writing an event based program is not the same as writing traditional programs. There is already an infinite loop running, and like any infinite loop, if you place another loop inside, the outer loop can't continue until the inner loop finishes. Since the outer loop is what causes the screen to refresh and events to be processed, inner loops effectively freeze your app until they are done.
Since there is already a loop running you don't need to create another loop. All you need to do is add little jobs to the event queue one at a time. Each job is, in effect, one iteration of your inner loop.
For example, if you wanted to write an inner loop like this:
for i in range(10):
print "i:", i
... you would instead add an event to the event queue and each time the event loop iterates (or more precisely, each time it finishes processing any other events) it will do one iteration of your loop. You do it like this:
def do_one_iteration(i):
print "i:", i
if i < 9:
root.after_idle(do_one_iteration, i+1)
Then, after the first time you call do_one_iteration, it will place the next iteration of itself on the event queue, and continue to do so until it decides it is done.
Typically you would call do_one_iteration when the user presses a button (eg: the "start" button). Call it once, then it does one bit of work (ie: moving the icicle down a couple of pixels) and then reschedules itself.
In game development you might have a function called update_display which is in charge of redrawing everything. It could, for example, subtract 1 from the Y coordinate of each icicle. You can add bindings for the left and right arrows to move the player (by incrementing or decrementing the X coordinate), and your update function would use these new coordinates to redraw the player.
By the way, you can slow down your program by using after instead of after_idle to call the function after a slight delay. This delay can be used to control the frame rate. For example, assuming the update is nearly instantaneous (and it probably will be in your case), calling after with an argument of 41 (milliseconds) yields a framerate of approximately 24 fps (24 frames times 41 milliseconds equals 984 milliseconds, or roughly 24 frames per second)
It may sound complicated but it's really pretty easy in practice once you do it once or twice.

Categories