how to stop timer at 20 minutes - python

I am making a timer that starts when the user hits "space" and stops on "p", showing the ending time. I can I stop it at a maximum time of 20 minutes? Is there something like
if time_passed==20:
break
My code:
from turtle import*
from datetime import datetime
...
def start():
undo()
global break1, timerint, startime
break1 = 0
startime = datetime.now()
while True:
timerint = datetime.now()-startime
write(timerint,font=("Arial",50))
undo()
if break1 == 1:
break
def stop():
global break1, timerint, startime
timerint=datetime.now()-startime
write(timerint,font=("Arial",50))
break1 = 1
# Turtle placement code removed
onkeypress(start,"space")
onkeypress(stop,"p")
listen()

No, but you can always check elapsed time with the time.time() method.
import time
start = time.time()
while ...
....
now = time.time()
if now - start > 20 * 60:
break
That's the low-tech version. If you want more sophisticated things, such as a separate timer process, try a full browser search for "Python timer process".
Also, you might consider using Boolean values:
global timer_running, timerint, startime
timer_running = True
startime = datetime.now()
while timer_running:
timerint = datetime.now()-startime
write(timerint,font=("Arial",50))
undo()
def stop():
global timer_running, timerint, startime
timerint = datetime.now()-startime
write(timerint, font=("Arial", 50))
timer_running = False

I recommend getting rid of the while loop and instead build upon turtle's ontimer() events:
from turtle import Turtle, Screen
from datetime import datetime
FONT = ("Arial", 50)
def start():
global timer_running, start_time
if timer_running:
return
start_time = datetime.now()
timer_running = True
screen.ontimer(lambda time=start_time: automatic_stop(time), 20 * 60 * 1000)
screen.ontimer(update, 100)
def update():
if not timer_running:
return
timerint = datetime.now() - start_time
marker.undo()
marker.write(timerint, align='center', font=FONT)
screen.ontimer(update, 100)
def manual_stop():
global timer_running
if not timer_running:
return
timer_running = False
timerint = datetime.now() - start_time
marker.undo()
marker.write(timerint, align='center', font=FONT)
def automatic_stop(time):
global timer_running
if timer_running and start_time == time: # make sure *this* timer is still valid
timer_running = False
marker.undo()
marker.write("Expired!", align='center', font=FONT)
screen = Screen()
marker = Turtle(visible=False)
marker.penup()
marker.write("Hit 'space' to start timer; 'p' to stop", align='center', font=FONT)
start_time = None
timer_running = False
screen.onkeypress(start, "space")
screen.onkeypress(manual_stop, "p")
screen.listen()
screen.mainloop()
We pass automatic_stop() a copy of start_time so that when it wakes up in the distant future it can check if it is still a valid end event or not based on the current start_time. (If you work at the Tk level instead of turtle, you might be able to cancel the timer when no longer needed.)

Related

How can I use a single button to pause and unpause timer in tkinter? (without using pygame)

I'm working on a simple timer that counts down 30 mins for me to study and 5 mins for a break. So far, the start_timer function and count_down function work well, I just cannot figure out how to write the pause function. I did some research for a few days. Most articles are using pygame or to bind with different keys. I am wondering what function I should use for one tkinter button to pause/unpause my timer if something comes up and I want to pause the timer till I'm back.
Thank you #TimRoberts, I can pause the timer now. However, I don't know how to unpause the timer to let it continue counting down.
from tkinter import *
import math
WORK_MIN = 30
BREAK_MIN = 5
reps = 0
paused = False
# --------------------------- TIMER ---------------------------- #
def start_timer():
global reps
reps += 1
work_sec = WORK_MIN * 60
break_sec = BREAK_MIN * 60
if reps % 2 == 1:
title_label.config(text="Study")
count_down(work_sec)
else:
title_label.config(text="Break")
count_down(break_sec)
window.attributes('-topmost', 0)
# ------------------------ COUNTDOWN--------------------------- #
def count_down(count):
global paused
count_min = math.floor(count / 60)
count_sec = count % 60
if count_min < 10:
count_min = f"0{count_min}"
if count_sec < 10:
count_sec = f"0{count_sec}"
canvas.itemconfig(timer_text, text=f"{count_min}:{count_sec}" )
if count > 0:
if not paused:
count -= 1
window.after(1000, count_down, count-1)
else:
start_timer()
# ---------------------------- PAUSE ------------------------------- #
def pause_function():
global paused
paused = not paused
# ---------------------------- UI ------------------------------- #
window = Tk()
title_label = Label(text="Timer")
title_label.grid(column=1, row=0)
check_marks = Label(text="")
check_marks.grid(column=1, row=4)
canvas = Canvas(width=200, height=224, bg="lightblue")
timer_text = canvas.create_text(100, 128, text="00:00", fill="white", font=("Courier", 45, "bold"))
canvas.grid(column=1, row=1)
start_button = Button(text="Start", command=start_timer)
start_button.grid(column=0, row=2)
pause_button = Button(text="Pause", command=pause_function)
pause_button.grid(column=2, row=2)
window.mainloop()
You need to do the "after" call even if you're paused, otherwise you'll never notice when you unpause. Also, since you're decrementing count once, you don't need to do it again:
def count_down(count):
count_min = count // 60
count_sec = count % 60
canvas.itemconfig(timer_text, text=f"{count_min:02d}:{count_sec:02d}" )
if count:
if not paused:
count -= 1
window.after(1000, count_down, count)
else:
start_timer()
If you want to be tricky, you could use:
if count:
count -= not paused
since True is 1 and False is 0.

Python Tkinter Timer

I am trying to make a clock in Tkinter and I am trying to create a timer however, whenever I try to start it the label just remains at the number chosen.
For the clock, I have an update variable that is always refreshing so I am trying to incorporate the timer into that. This is the update code:
def Update():
now = datetime.now()
time = now.strftime("%H:%M:%S")
Time.config(text = f"{time}")
if TimerOn == True:
root.after(1000, Update)
root.after(1000, TimerUpdate)
else:
root.after(1000, Update)
The "TimerOn" Variable is set true here:
def Start():
Timer.config(font = ("Times New Roman", 50))
Timer.place(x = "160", y = "250")
TimerOn = True

After() method in tkinter for timer

I want to start a timer when the user clicks a button for the first time in my number click game. I tried to use the after method for this, but when I click a button, the timer stays at 0. The rest of the code works fine without any error messages.
Here's the code:
import tkinter as tk
from random import randint
# create window
window = tk.Tk()
window.title('Clicker')
# create list for random numbers
new_list = []
# define time count:
def time_event():
global current_time, after_id
if clock_started:
current_time += 1
clock["text"] = str(current_time)
after_id = clock.after(1000, time_event)
# define click event
def click_event(event):
global clock_started
new_button = event.widget
clicked_val = int(new_button["text"])
clock_started = True
if not clock_started:
clock_started = True
clock.after(1000, time_event)
if clicked_val == new_list[0]:
del new_list[0]
new_button["state"] = tk.DISABLED
if len(new_list) == 0:
clock.started = False
clock.after_cancel(after_id)
# create buttons
for i in range(25):
new_num = randint(1, 999)
while i in new_list:
new_num = randint(1, 999)
new_list.append(new_num)
new_list.sort()
new_button = tk.Button(window, text=new_num)
new_button.grid(column=i // 5, row=i % 5)
new_button.bind("<Button-1>", click_event)
# create clock
current_time = 0
clock = tk.Label(window, text=str(current_time))
clock.grid(column=2, row=6)
clock_started = False
# run game
window.mainloop()
In your code, clock_started has been initialized to True which implies that this condition if not clock_started: will not be satisfied to begin with and hence the timer doesn't work without giving an error. Your final click_event(event) should look like this:
def click_event(event):
global clock_started
new_button = event.widget
clicked_val = int(new_button["text"])
clock_started = False
if not clock_started:
clock_started = True
clock.after(1000, time_event)
if clicked_val == new_list[0]:
del new_list[0]
new_button["state"] = tk.DISABLED
if len(new_list) == 0:
clock.started = False
clock.after_cancel(after_id)

Raspberry PI Button Push Listener

Is it possible to have button push listener in Python for the Raspberry Pi. I have a non-latching button going to a GPIO. I want to run some python code the first time the button is pushed. Then I want the code to stop on the second button push no matter where it is in the first line of code.
I've used a toggle bit variable called "flag" to register button pushes but obviously there is no listener to determine when the second push is made.
#!/usr/bin/env python
import RPi.GPIO as GPIO
import time
Button = 16 # pin16
def setup():
GPIO.setmode(GPIO.BOARD) # Numbers GPIOs by physical location
GPIO.setup(Button, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # Button Input
def motorcontrol():
flag=0
while True:
j=GPIO.input(16)
if j==1: #Robot is activated when button is pressed
flag=1
print "Robot Activated",j
while flag==1:
time.sleep(5)
print "Robot Activated for 5 seconds"
time.sleep(5)
print "Robot Activated for 10 seconds"
time.sleep(5)
print "Robot Activated for 15 seconds"
j=GPIO.input(16)
if j==1: #De activate robot on pushing the button
flag=0
print "Robot DeActivated",j
destroy()
def destroy():
GPIO.cleanup() # Release resource
if __name__ == '__main__': # Program start from here
setup()
try:
motorcontrol()
except KeyboardInterrupt: # When 'Ctrl+C' is pressed, the child program destroy() will be executed.
destroy()
You can't use sleep() this way because your while loop can't check your botton. You have to loop all time and check if it is time to display text.
You can use small sleep() to use lees CPU.
import time
current_time = time.time()
text_1 = current_time + 5
text_2 = current_time + 10
text_3 = current_time + 15
flag = True
while flag:
# TODO: check your button and change `flag`
current_time = time.time()
if text_1 and current_time >= text_1:
print("5 seconds")
text_1 = None # to stop displaying
# or show again after 5 seconds
#text_1 = current_time + 5
if text_2 and current_time >= text_2:
print("10 seconds")
text_2 = None # to stop displaying
if text_3 and current_time >= text_3:
print("15 seconds")
text_3 = None # to stop displaying
flag = False
#time.sleep(0.1)
Or more like in most GUI
import time
# --- functions ---
def callback_1():
print("5 seconds")
# add new task to list
tasks.append( (current_time + 5, callback_1) )
def callback_2():
print("10 seconds")
def callback_3():
print("15 seconds")
def callback_4():
global flag
flag = False
# --- main ---
current_time = time.time()
tasks = []
tasks.append( (current_time + 5, callback_1) )
tasks.append( (current_time + 10, callback_2) )
tasks.append( (current_time + 15, callback_3) )
tasks.append( (current_time + 17, callback_4) )
flag = True
while flag:
# TODO: check your button
current_time = time.time()
# this way I execute task and remove from list
new_tasks = []
for t, c in tasks:
if current_time >= t:
c()
else:
new_tasks.append( (t,c) )
tasks = new_tasks
#time.sleep(0.1)
EDIT: I don't have RPi so I try to simulate it using own class GPIO - but maybe it will work on your computer. It shows where you should put code.
#!/usr/bin/env python
import RPi.GPIO as GPIO
import time
'''
#
# for test only - instead of `import RPi.GPIO as GPIO`
#
# simulate button press
current_time = time.time()
button_1 = current_time + 2
button_2 = current_time + 10
class GPIO:
BOARD = None
IN = None
PUD_DOWN = None
#staticmethod
def setmode(a):
pass
#staticmethod
def setup(a, b, pull_up_down=None):
pass
#staticmethod
def input(a):
global button_1, button_2
current_time = time.time()
if button_1 and current_time >= button_1:
button_1 = None
return 1
if button_2 and current_time >= button_2:
button_2 = None
return 1
return 0
#staticmethod
def cleanup():
pass
'''
Button = 16 # pin16
def setup():
GPIO.setmode(GPIO.BOARD) # Numbers GPIOs by physical location
GPIO.setup(Button, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # Button Input
def motorcontrol():
flag = False
while True:
j = GPIO.input(16)
if j == 1:
flag = True
print "Robot Activated", j
current_time = time.time()
text_1 = current_time + 5
text_2 = current_time + 10
text_3 = current_time + 15
while flag:
j = GPIO.input(16)
if j == 1:
flag = False
print "Robot DeActivated", j
destroy()
current_time = time.time()
if text_1 and current_time >= text_1:
print "5 seconds"
text_1 = None # to stop displaying
# or show again after 5 seconds
#text_1 = current_time + 5
if text_2 and current_time >= text_2:
print "10 seconds"
text_2 = None # to stop displaying
if text_3 and current_time >= text_3:
print "15 seconds"
text_3 = None # to stop displaying
flag = False
time.sleep(0.1)
def destroy():
GPIO.cleanup()
if __name__ == '__main__':
setup()
try:
motorcontrol()
except KeyboardInterrupt:
destroy()

How to make a mouse click do something at a specific point?

how can I make the mouse do something once it's clicking at a certain point in Zelle's graphics? What I am trying to do is make my stopwatch start when I click the "startbutton" image. However, I am obviously doing something wrong because my program either crashes or doesn't do anything.
from graphics import *
import time
#from tkinter import *
win = GraphWin('Stopwatch', 600, 600)
win.yUp()
#Assigning images
stopWatchImage = Image(Point (300, 300), "stopwatch.png")
startImage = Image(Point (210, 170), "startbutton.png")
stopImage = Image(Point (390, 170), "stopbutton.png")
lapImage = Image(Point (300, 110), "lapbutton.png")
stopWatchImage.draw(win)
startImage.draw(win)
stopImage.draw(win)
lapImage.draw(win)
sec = 0
minute = 0
hour = 0
def isBetween(x, end1, end2):
'''Return True if x is between the ends or equal to either.
The ends do not need to be in increasing order.'''
return end1 <= x <= end2 or end2 <= x <= end1
def isInside(point, startImage):
'''Return True if the point is inside the Rectangle rect.'''
pt1 = startImage.getP1()
pt2 = startImage.getP2()
return isBetween(point.getX(), pt1.getX(), pt2.getX()) and \
isBetween(point.getY(), pt1.getY(), pt2.getY())
def getChoice(win): #NEW
'''Given a list choicePairs of tuples with each tuple in the form
(rectangle, choice), return the choice that goes with the rectangle
in win where the mouse gets clicked, or return default if the click
is in none of the rectangles.'''
point = win.getMouse()
if isInside(point, startImage):
time.sleep(1)
sec += 1
timeText.setText(sec)
timeText.setText('')
while sec >= 0 and sec < 61:
#Displaying Time
timeText = Text(Point (300,260), str(hour) + ":" + str(minute) + ":" + str(sec))
timeText.setSize(30)
timeText.draw(win)
time.sleep(1)
sec += 1
timeText.setText(sec)
timeText.setText('')
#Incrementing minutes,hours
if sec == 60:
sec = 0
minute += 1
if minute == 60:
sec = 0
minute = 0
hour += 1
return default
def layout()
getChoice(win)
layout()
I can't seem to get it to work.
Edit: added the rest of my code for clarification.
You can use setMouseHandler to assign function which will called when you click in window.
In example if you click in left part of window then it draws rectangle, if you click in right part of window then it draws circle.
You can open file graphics.py and see all code. It is the fastest method to can see what functions you can use.
from graphics import *
# --- functions ---
def on_click(point):
# inform function to use global variable
global win
if point.x > win.width//2:
c = Circle(point, 10)
c.draw(win)
else:
a = Point(point.x - 10, point.y - 10)
b = Point(point.x + 10, point.y + 10)
r = Rectangle(a, b)
r.draw(win)
def main():
# inform function to use global variable
global win
win = GraphWin("My Circle", 500, 500)
win.setMouseHandler(on_click)
win.getKey()
win.close()
# --- start ---
# create global variable
win = None
main()
BTW: graphics uses Tkinter which has widgets Button, Label, Text, etc. It can use canvas.create_window() to add widget to canvas.
Tkinter has also function after(miliseconds, function_name) which lets you execute function periodically - ie. to update time.
Example
from graphics import *
import datetime
# --- classes ---
class _Widget():
def __init__(self, x, y, w, h, **options):
self.x = x
self.y = y
self.w = w
self.h = h
self.options = options
def draw(self, canvas, **options):
return None
def set(self, **options):
self.widget.config(options)
def get(self, **options):
self.widget.cget(options)
class Button(_Widget):
def draw(self, canvas, **options):
x, y = canvas.toScreen(self.x, self.y) # ???
self.widget = tk.Button(canvas, self.options)
return canvas.create_window((x, y), options, width=self.w, height=self.h, window=self.widget, anchor='nw')
class Label(_Widget):
def draw(self, canvas, **options):
x, y = canvas.toScreen(self.x, self.y) # ???
self.widget = tk.Label(canvas, self.options)
return canvas.create_window((x, y), options, width=self.w, height=self.h, window=self.widget, anchor='nw')
# --- functions ---
def on_start():
#global b1, b2
global running
b1.set(state='disabled')
b2.set(state='normal')
running = True
# run first time
update_time()
print("START")
def on_stop():
#global b1, b2
global running
b1.set(state='normal')
b2.set(state='disabled')
l.set(text="Controls:")
running = False
print("STOP")
def update_time():
#global l
#global running
if running:
l.set(text=datetime.datetime.now().strftime("%H:%M:%S"))
# run again after 1000ms (1s)
win.after(1000, update_time)
# --- main ---
def main():
global win, l, b1, b2
win = GraphWin("My Buttons", 500, 500)
l = Label(0, 0, 100, 50, text="Controls:")
l.draw(win)
b1 = Button(100, 0, 100, 50, text="START", command=on_start)
b1.draw(win)
b2 = Button(200, 0, 100, 50, text="STOP", command=on_stop, state='disabled')
b2.draw(win)
win.getKey()
win.close()
# --- global variable to access in functions ---
win = None
l = None
b1 = None
b2 = None
running = False
# --- start ---
main()
Tkinter: Canvas, Button, other
EDIT: working example
from graphics import *
def isBetween(x, end1, end2):
return end1 <= x <= end2 or end2 <= x <= end1
def isInside(point, startImage):
x = startImage.getAnchor().getX()
y = startImage.getAnchor().getY()
w = startImage.getWidth()/2
h = startImage.getHeight()/2
pt1_x = x - w
pt1_y = y - h
pt2_x = x + w
pt2_y = y + h
return isBetween(point.getX(), pt1_x, pt2_x) and \
isBetween(point.getY(), pt1_y, pt2_y)
def getChoice(event):
global hour, minute, sec
global running
point = Point(round(event.x), round(event.y))
if isInside(point, startImage):
sec = 0
minute = 0
hour = 0
running = True
update_time()
if isInside(point, stopImage):
running = False
def update_time():
global hour, minute, sec
#global timeText
#global win
sec += 1
if sec == 60:
sec = 0
minute += 1
if minute == 60:
minute = 0
hour += 1
timeText.setText('{}:{}:{}'.format(hour, minute, sec))
if running:
win.after(1000, update_time)
else:
timeText.setText('')
def layout():
global win
global stopWatchImage
global startImage
global stopImage
global lapImage
global timeText
win = GraphWin('Stopwatch', 600, 600)
#win.yUp()
#Assigning images
stopWatchImage = Image(Point(300, 300), "stopwatch.png")
startImage = Image(Point(210, 170), "startbutton.png")
stopImage = Image(Point(390, 170), "stopbutton.png")
lapImage = Image(Point(300, 110), "lapbutton.png")
#Drawing images
stopWatchImage.draw(win)
startImage.draw(win)
stopImage.draw(win)
lapImage.draw(win)
timeText = Text(Point(300,260), '')
timeText.setSize(30)
timeText.draw(win)
win.setMouseHandler(getChoice)
win.getKey()
# --- global variable ---
win = None
stopWatchImage = None
startImage = None
stopImage = None
lapImage = None
timeText = None
running = False
# --- start ---
layout()

Categories