RaspberryPi Stopwatch (TKinter) with Trigger - python-3.x

i would like to build a stopwatch for my carrera track which i built a little bit bigger.
So I have bought a Raspberry Pi 3 with an additional 7 "touch screen and the individual modules for triggering.
Everything works fine individually.
Now I found a great stopwatch on the net which I also used. Works really great.
In another script, that I've written by myself, the triggering with the gpios works also fantastic.
Now I want to combine both and fail.
Does anyone have an idea or suggested solution where my mistake is?
Here is my code
#!/usr/bin/python
import tkinter as tk
import RPi.GPIO as GPIO
import time
GPIO_TRIGGER_PIN = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_TRIGGER_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setwarnings(False)
def update_timeText():
if (state):
global timer
timer[2] += 1
if (timer[2] >= 100):
timer[2] = 0
timer[1] += 1
if (timer[1] >= 60):
timer[0] += 1
timer[1] = 0
timeString = pattern.format(timer[0], timer[1], timer[2])
timeText.configure(text=timeString)
root.after(10, update_timeText)
def start():
global state
state = True
print('Pressed Start')
def stop():
global state
state = False
print('Pressed Stop')
def reset():
global timer
timer = [0, 0, 0]
timeText.configure(text='00:00:00')
print('Pressed Reset')
while GPIO.input(GPIO_TRIGGER_PIN) == True:
if GPIO.input(GPIO_TRIGGER_PIN):
print('CAR DETECTED')
time.sleep(0.1)
state = False
# BULDING TKinter GUI
root = tk.Tk()
root.wm_title('Stopwatch')
timer = [0, 0, 0]
pattern = '{0:02d}:{1:02d}:{2:02d}'
timeText = tk.Label(root, text="00:00:00", font=("Helvetica", 150))
timeText.pack()
startButton = tk.Button(root, text='Start', command=start)
startButton.pack()
stopButton = tk.Button(root, text='Stop', command=stop)
stopButton.pack()
resetButton = tk.Button(root, text='Reset', command=reset)
resetButton.pack()
update_timeText()
root.mainloop()
Currently I get the trigger in the console as output "CAR DETECTED". However, I do not get the TKinter panel.
If I remove
while GPIO.input(GPIO_TRIGGER_PIN) == True:
if GPIO.input(GPIO_TRIGGER_PIN):
print('CAR DETECTED')
time.sleep(0.1)
then the display appears and works. Without triggering.
If I put it all down, I get also the panel but it also triggers nothing more.
Any ideas ?
Thanks for help

Not really my area of expertise, but since nobody else answered, I'll give it a shot.
I think you want to use GPIO.add_event_callback()* to add a callback that gets called when an event happens.
# Callback function
def on_trigger(channel_number):
# Just update the flag
global state
state = False
# Set the callback
GPIO.add_event_callback(GPIO_TRIGGER_PIN , callback=on_trigger)
Then, delete the while loop.
*According to the docs, you need the darksidesync library to use this function. Instead of the callback, you could also create a separate thread. Personally, I would prefer the callback.
from threading import Thread
def thread_function(arg):
global state
# Listen for state change
while GPIO.input(GPIO_TRIGGER_PIN) == True:
if GPIO.input(GPIO_TRIGGER_PIN):
# Update var
state = False
print('CAR DETECTED')
time.sleep(0.1)
thread = Thread(target = thread_function)
thread.start()
Hope this helps.

Related

Tkinter window freezes / lags whenever the user inputs text to text field

I am currently working on a server / client chat program which works fine on terminal but in trying to migrate it into a windowed program I've come across a few errors which I have managed to solve except for this one, for whatever reason, in the GUI class running on the main thread seems to lag considerably whenever the user focuses their attention on it or whenever they try to interact with it, I've tried to use threading but I'm not sure what the problem is.
...
class GUI():
def __init__(self):
self.txtval = []
self.nochngtxtval = []
self.root = tk.Tk()
self.root.title("Chatroom")
self.root.geometry("400x200")
self.labl = tk.Label(self.root, text="", justify=tk.LEFT, anchor='nw', width=45, relief=tk.RIDGE, height=8)
self.labl.pack()
count = 1
while events["connected"] == False:
if events["FError"] == True:
quit()
self.labl["text"] = "Loading"+"."*count
count += 1
if count > 3:
count = 1
self.labl["text"] = ""
self.inputtxt = tk.Text(self.root,height=1,width=40,undo=True)
self.inputtxt.bind("<Return>", self.sendinput)
self.sendButton = tk.Button(self.root, text="Send")
self.sendButton.bind("<Button-1>", self.sendinput)
self.sendButton.pack(padx=5,pady=5,side=tk.BOTTOM)
self.inputtxt.pack(padx=5,pady=5,side=tk.BOTTOM)
uc = Thread(target=self.updatechat,daemon=True)
uc.start()
self.root.protocol("WM_DELETE_WINDOW", cleanAndClose)
self.root.mainloop()
def updatechat(self):
global events
while True:
if events["recv"] != None:
try:
current = events["recv"]
events["recv"] = None
... # shorten to reasonable length (45 characters)
self.labl['text'] = ''
self.txtval = []
self.nochngtxtval.append(new+"\n")
for i in range(len(nochngtxtval)):
self.txtval.append(nochngtxtval[i])
self.txtval.pop(len(self.txtval)-1) # probably not necessary
self.txtval.append(new+"\n") # probably not necessary
self.txtval[len(self.txtval)-1] = self.txtval[len(self.txtval)-1][:-1]
for i in range(len(self.txtval)):
self.labl['text'] = self.labl['text']+self.txtval[i]
except Exception as e:
events["Error"] = str(e)
pass
def sendinput(self, event):
global events
inp = self.inputtxt.get("1.0", "end").strip()
events["send"] = inp
self.inputtxt.delete('1.0', "end")
... # start the logger and server threads
GUI()
I'm using my custom encryption which is linked on github https://github.com/Nidhogg-Wyrmborn/BBR
my server is named server.py in the same git repo as my encryption.
EDIT:
I added a test file which is called WindowClasses.py to my git repo which has no socket interactions and works fine, however the problem seems to be introduced the moment i use socket as displayed in the Client GUI. I hope this can help to ascertain the issue.

Separating Tkinters GUI and control of the application

1) What is my goal:
I’m creating an application that should read data every 60s from ModBusServer, append those data to Graphs and then when the app is closed save the data to excel file.
Site note:
The process of reading data from ModBusServer and appending them to graphs should start after a start button is pressed.
And end after stop button is pressed OR when ModBusServer sends a request to stop.
2) What I have so far:
I created the GUI without any major problems as a class “GUI_komora”.
Everything there works just fine.
3) What is the problem:
But now I’m lost on how to approach the “read data every 60 seconds”, and overall how to control the application.
I did some research on threading but still I’m confused how to implement this to my application.
I learned how to make functions run simultaneously in this tutorial.
And also how to call a function every few seconds using this question.
But none of them helped me to learn how to control the overall flow of the application.
If you could redirect me somewhere or tell me about a better approach I would be really glad.
Some of my code:
from tkinter import *
from GUI_komora import GUI
root = Tk()
my_gui = GUI(root) #my GUI class instance
#main loop
root.mainloop()
"""
How do I achieve something like this???
whenToEnd = False
while whenToEnd:
if step == "Inicialzation":
#inicializace the app
if step == "ReadData":
#read data every 60 seconds and append them to graphs
if step == "EndApp"
#save data to excel file and exit app
whenToEnd = True
"""
Here is an example of a loop that takes a decision (every 60 sec in your case) and pushes the outcome of the decision to tkinter GUI: https://github.com/shorisrip/PixelescoPy/blob/master/base.py
Parts:
main thread - starts tkinter window
control thread - reads some data and decides what to show in GUI
GUI class - has a method "add_image" which takes input an image and displays on GUI.(add_data_to_graph maybe in your case). This method is called everytime by the control thread.
Snippets:
def worker(guiObj, thread_dict):
# read some data and make decision here
time.sleep(60)
data_to_show = <outcome of the decision>
while some_logic:
pictureObj = Picture(chosen, timer)
pictureObj.set_control_thread_obj(thread_dict["control_thread"])
guiObj.set_picture_obj(pictureObj)
pictureObj.display(guiObj)
# do post display tasks
guiObj.quit_window()
# Keep GUI on main thread and everything else on thread
guiObj = GuiWindow()
thread_list = []
thread_dict = {}
thread_for_image_control = threading.Thread(target=worker, args=(guiObj,
thread_dict))
thread_dict["control_thread"] = thread_for_image_control
thread_list.append(thread_for_image_control)
thread_for_image_control.start()
guiObj.run_window_on_loop()
# thread_for_image_control.join()
Code for Picture class:
class Picture:
def __init__(self, path, timer):
self.path = path
self.timer = timer
self.control_thread_obj = None
def set_control_thread_obj(self, thread_obj):
self.control_thread_obj = thread_obj
def display(self, guiObj):
image_path = self.path
guiObj.add_image(image_path)
time.sleep(self.timer)
Code for GUI class
class GuiWindow():
def __init__(self):
self.picture_obj = None
self.root = Tk()
self.image_label = None
self.image = None
self.folder_path = None
self.timer = None
self.root.protocol("WM_DELETE_WINDOW", self.exit_button)
def add_image(self, image_path):
resized_img = self.resize(image_path)
image_obj = ImageTk.PhotoImage(resized_img)
image_label = Label(self.root, image=image_obj,
height=resized_img.height,
width=resized_img.width)
self.image = image_obj # DO NOT REMOVE - Garbage collector error
if self.image_label is not None:
self.remove_image()
image_label.grid(row=0, column=0, columnspan=3)
self.image_label = image_label
Here based on my control loop thread I am changing image (in your case graph data) of the tkinter GUI.
Does this help?

lcdnumber signal and slot

I have put together (with lots of researching questions from this site, thank you) some code which will, in part, display raspberry pi input pin voltages on qlcdnumber widgets in my qt creator gui. The code runs perfectly, the gui pops up, and the pin voltage prints to the terminal (temporary) and the buttons are functional in the gui, turning other pins high and low. I don't get any errors, but the issue is that the signal won't display on the gui lcd. I have researched all I can to no success and I feel as though I'm guessing at it now. I would appreciate any input and instruction. I've learned a lot but still have far to go so any help would be great. I am using Raspbian and Qt4/Qt Creator 3.2.1
Here is the entire code and once again thank you for any instruction:
import sys
import RPi.GPIO as GPIO
import time
import re
from PyQt4 import QtGui, uic, QtCore
import spidev
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
o2zero = 26
o2span = 19
cozero = 13
cospan = 6
co2zero = 5
co2span = 21
status = "nil"
o2_channel = 0
o2_temp_channel = 1
spi = spidev.SpiDev()
spi.open(0,0)
class MyWindow(QtGui.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
uic.loadUi('mainwindow.ui', self)
self.o2_zero_up.clicked.connect(self.o2zeroup)
self.o2_zero_down.clicked.connect(self.o2zerodown)
self.o2_span_up.clicked.connect(self.o2spanup)
self.o2_span_down.clicked.connect(self.o2spandown)
self.co_zero_up.clicked.connect(self.cozeroup)
self.co_zero_down.clicked.connect(self.cozerodown)
self.co_span_up.clicked.connect(self.cospanup)
self.co_span_down.clicked.connect(self.cospandown)
self.co2_zero_up.clicked.connect(self.co2zeroup)
self.co2_zero_down.clicked.connect( self.co2zerodown)
self.co2_span_up.clicked.connect(self.co2spanup)
self.co2_span_down.clicked.connect(self.co2spandown)
self.close_button.clicked.connect(self.gpiocleanup)
self.thread = O2_Channel()
self.thread.o2_concentration.connect(self.onChangeValue)
self.thread.start()
self.show()
def onChangeValue(self, values):
o2_volts = values
def o2zeroup(self):
GPIO.setup(o2zero, GPIO.OUT)
GPIO.output(o2zero, 1)
def o2zerodown(self):
GPIO.setup(o2zero, GPIO.OUT)
GPIO.output(o2zero, 0)
def o2spanup(self):
GPIO.setup(o2span, GPIO.OUT)
GPIO.output(o2span, 1)
def o2spandown(self):
GPIO.setup(o2span, GPIO.OUT)
GPIO.output(o2span, 0)
def cozeroup(self):
GPIO.setup(cozero, GPIO.OUT)
GPIO.output(cozero, 1)
def cozerodown(self):
GPIO.setup(cozero, GPIO.OUT)
GPIO.output(cozero, 0)
def cospanup(self):
GPIO.setup(cospan, GPIO.OUT)
GPIO.output(cospan, 1)
def cospandown(self):
GPIO.setup(cospan, GPIO.OUT)
GPIO.output(cospan, 0)
def co2zeroup(self):
GPIO.setup(co2zero, GPIO.OUT)
GPIO.output(co2zero, 1)
def co2zerodown(self):
GPIO.setup(co2zero, GPIO.OUT)
GPIO.output(co2zero, 0)
def co2spanup(self):
GPIO.setup(co2span, GPIO.OUT)
GPIO.output(co2span, 1)
def co2spandown(self):
GPIO.setup(co2span, GPIO.OUT)
GPIO.output(co2span, 0)
def gpiocleanup(self):
GPIO.cleanup()
def closeEvent(self, event):
self.thread.stop()
self.thread.quit()
self.thread.wait()
self.thread.deleteLater()
print ("GPIO CleanUP")
GPIO.cleanup()
event.accept()
def ReadChannel(channel):
adc = spi.xfer2([1,(8+channel)<<4,0])
data = ((adc[1]&3) << 8) + adc[2]
return data
def ConvertVolts(data, places):
volts = (data * 3.3) / float(1023)
volts = round(volts,places)
return volts
class O2_Channel(QtCore.QThread):
o2_concentration = QtCore.pyqtSignal(int)
def __init__(self):
QtCore.QThread.__init__(self)
self.mRunning = True
def run(self):
while self.mRunning:
o2_level = ReadChannel(o2_channel)
o2_volts = ConvertVolts(o2_level,2)
print("{}".format(o2_volts))
self.o2_concentration.emit(o2_volts)
delay = .2
time.sleep(delay)
def stop(self):
self.mRunning = False
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MyWindow()
sys.exit(app.exec_())
In a different post, eyllanesc set me up with the signal and slot I needed to display the signal to my gui lcd but I didn't understand it well enough to know just what he did. I've spent the last few days reading about this and finally see just what he did. Everything works and works just like I want it to.
For anyone who might have the same confusion I had, here was my main problem:
def onChangeValue(self, values):
o2_volts = values
I am so new at this that I didn't realize I needed to actually put my specified value in place of the word "values" in the above code. A little embarrassing but part of learning I guess. The other problem was here:
o2_concentration = QtCore.pyqtSignal(int)
I didn't realize it but after I fixed the "values", the lcd was working but was reading 0 and I assumed it wasn't working. Using a photocell to get voltage, I was getting .25 when printing to the terminal but 0 to the lcd. Again being new I didn't think about (int) not being correct for what I wanted. Changing it to (float) fixed the problem. Everything works great.
The my script has grown since the original post but if anyone would like to see it I would be happy to post it.
This is a wonderful site. Thank for everything.

UPDATE 2: How Do I Stop a Function in a while loop?

Update:
So I was successful in implementing threading to allow the GUI to remain unblocked while the process is running. Now i am trying to figure out how to get this while loop to break and still function properly.
I tried implementing a second variable the while statement sees, as a flag, to attempt to break the while loop after running the PUMP function inside of it once. However, now the PUMP function doesn't run at all. The GPIO pin never goes high.
What I'm looking for this to do:
-Press button.
-Sets Flag to 1
-Runs RUN() function in thread if float switch is high/ signals low water if float switch is low
- RUN() checks status of flag and float switch while running PUMP() function
- PUMP() turns GPIO pin high, and after 5 secs, calls the OFF() function
- OFF () sets flag to 0 and also sets pump GPIO to low
If during the PUMP() the float switch goes low, it should trigger and call the LOW() function, stopping the pump by setting GPIO pin to low and displaying status. This also sets the flag to 0 as well.
Code:
from tkinter import *
import tkinter.font
import RPi.GPIO as GPIO
import threading
#Variables
Flag = 0
#GPIO Setup
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(16, GPIO.OUT) #Water Pump
GPIO.setup(18, GPIO.IN) #Tank Float Switch
GPIO.output(16, GPIO.LOW)
#Window Setup
win = Tk()
win.title("Pump Test")
win.geometry("150x150+0+0")
#Label Setup
Label (win, text="Water System", fg="red", bg="black", font="24").grid(row=0, column=0)
#Functions
def RUN ():
while GPIO.input(18) and Flag == 1:
PUMP()
if Flag == 0:
OFF()
elif GPIO.input(18) == False:
LOW()
def OFF ():
Flag = 0
GPIO.output(16, GPIO.LOW)
WTR.config(text="Water", bg="grey")
def LOW ():
Flag = 0
GPIO.output(16, GPIO.LOW)
WTR.config(text="LOW WATER", bg="red")
def WTR ():
Flag = 1
if GPIO.input(18):
threading.Thread(target=RUN).start()
if GPIO.input(18)== False:
threading.Thread(target=LOW).start()
def PUMP ():
GPIO.output(16, GPIO.HIGH)
win.after(5000, OFF)
#Buttons
WTR = Button(win, text="Water", bg="grey", command = WTR, height = 2, width = 8)
WTR.grid(row=1, column=0) #Water Pump Control
mainloop()
To ensure a UI remains responsive to user events (mouse clicks etc) and also to system events (like exposure and repainting) you should never enter a long lived loop in a function and never use sleep. Instead Tkinter provides the after method to allow you to schedule something to be done after some interval. This call adds your call into the event queue and its gets processed in due time by the code called by mainloop. For something that should occur after a delay obviously after(num_millis) is used. If you need to poll the state of a pin, then use a short time and in the handler, set another after call to call your polling function again. Note you can cancel after calls provided you retain the id value that is returned when you call the method.
Don't use time.sleep. No UI events will be processed during the sleep and the UI will be dead. Use after.
Your Flag is a module-level variable.
If you want to modify that in a function (without passing it as an argument), you need to mark it as global in the function.
Observe:
In [1]: flag = 0
In [2]: def foo(num):
...: flag = num
...:
In [3]: flag
Out[3]: 0
In [4]: foo(4)
In [5]: flag
Out[5]: 0
In [6]: foo(12)
In [7]: flag
Out[7]: 0
Calling foo sets a flag, but this is local to the function! It has no effect on the module-level object.
You have to explicitly say that you want to modify the module-level variable:
In [8]: def bar(num):
...: global flag
...: flag = num
...:
In [9]: bar(4)
In [10]: flag
Out[10]: 4
In [11]: bar(7)
In [12]: flag
Out[12]: 7
Thanks #patthoyts and #RolandSmith for the insight that helped lead me to the answer.
2 things they stated were helpful - Not using time.sleep and making sure I was using a global variable.
With some re-work on the Flag idea, and not sleeping but creating a check function to see how much time has passed. Deleted threading for now as it wasn't dire to the GUI process. Big shout out to Tom Slick with the behind the scenes help as well!
from tkinter import *
import tkinter.font
import RPi.GPIO as GPIO
import time
GPIO Setup
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(16, GPIO.OUT) # Water Pump
GPIO.setup(18, GPIO.IN) # Tank Float Switch
GPIO.output(16, GPIO.LOW)
time_to_run = 60 # time to run the pump in seconds
time_running = time.time() - 1
#Window Setup
win = Tk()
win.title("Pump Test")
win.geometry("150x150+0+0")
#Label Setup
Label (win, text="Water System", fg="red", bg="black", font="24").grid(row=0, column=0)
#Functions
def off():
WTR.config(text="LOW WATER", bg="red")
GPIO.output(16, GPIO.LOW)
def wtr():
global time_running
time_running = time.time() + time_to_run
if GPIO.input(18) and not GPIO.input(16):
pump_on()
elif GPIO.input(16):
pump_off()
def pump_on():
WTR.config(text="Water", bg="green")
GPIO.output(16, GPIO.HIGH)
# added to turn the pump off manualy
def pump_off():
WTR.config(text="Water", bg="grey")
GPIO.output(16, GPIO.LOW)
def check_pump():
# if the water level goes low turn the pump off
# and show low water level
if not GPIO.input(18):
off()
# if a set time has passed then shut the pump off it it is running
if time_running <= time.time() and GPIO.input(16):
pump_off()
win.after(100, check_pump) # call this function every 100 miliseconds
#Buttons
WTR = Button(win, text="Water", bg="grey", command = wtr, height = 2, width = 8)
WTR.grid(row=1, column=0) #Water Pump Control
check_pump() # must be called once to start the after function
mainloop()

Timed Availability of controls in tkinter GUI

I'm working on a program that will stop users from changing a label after a random amount of time. There are two buttons, start and next, when the user presses start the start button is destroyed but is supposed to come back after a randomly selected amount of time. I tried to have the start button trigger a flag that starts a timer. When the timer reaches a certain value (count_to+1) the flag should go to zero, the start button should reappear, and the label should read end. The flag never seems to switch and the timer never initiates though. Can anyone tell me what I did wrong? and maybe point me towards a solution? Hear is the code:
import sys
from tkinter import *
import random
import time
mGui = Tk()
mGui.geometry('450x450+200+200')
mGui.title('Letters')
stored = ['A', 'b', 'c', 'd']
count_down = [10,20,30,40,50,60]
global count_to
global countFlag
count_to = IntVar()
countFlag = 0
Sec = 0
def run_counter():
count_to = random.choice(count_down)
while countFlag == 1:
Sec+=1
print(sec)
if Sec == count_to+1:
countFlag = 0
newbutton.destroy()
startbutton.grid(row=2,column=1)
phrase.configure(text='End')
return
def change_phrase():
fish = StringVar()
fish = random.choice(stored)
stored.remove(fish)
phrase.configure(text=fish)
#to help with debug
print(countFlag)
print(Sec)
print(count_to)
return
def start_count():
countFlag = True
count_to = random.choice(count_down)
print(countFlag)
startbutton.destroy()
run_counter
return
phrase = Label(mGui,text='Letter',fg='red',bg='blue')
phrase.grid(row=0,column=0, sticky=S,columnspan=2)
startbutton =Button(mGui, text='start',fg='black',bg='green',command=start_count)
startbutton.grid(row=2,column=1)
newbutton = Button(mGui,text='NEXT',fg='black',bg='red',command=change_phrase)
newbutton.grid(row=2,column=0)
#mEntry = Entry(mGui,textvariable=ment)
#mEntry.grid(row=3,column=0)
mGui.mainloop()
Tkinter programming becomes much less messy and confusing once you learn to use classes. Use Tkinter's after() method to call a function every "x" amount of time until the allotted time has elapsed.
import random
import sys
if sys.version_info[0] < 3:
import Tkinter as tk ## Python 2.x
else:
import tkinter as tk ## Python 3.x
class ButtonDisappear():
def __init__(self, root):
self.root=root
self.startbutton=tk.Button(root, text='disappear', fg='black',
bg='green', command=self.disappear)
self.startbutton.grid(row=2,column=1)
self.lab=tk.Label(self.root, text="", bg="lightblue")
def disappear(self):
## remove button
self.startbutton.grid_forget()
## grid label for time
self.lab.grid(row=0, column=0)
## "random" number
self.stop_time=random.choice([1, 2, 3, 4, 5])*1000
self.elapsed=0
self.root.after(100, self.time_it)
def time_it(self):
self.elapsed += 100
## function calls itself until time has finished
if self.elapsed < self.stop_time:
self.lab["text"]="%d of %d" % (self.elapsed, self.stop_time)
self.root.after(100, self.time_it)
## time elapsed so remove label and restore button
else:
self.lab.grid_forget()
self.startbutton.grid(row=2,column=1)
m_gui = tk.Tk()
m_gui.geometry('450x450+200+200')
m_gui.title('Letters')
B=ButtonDisappear(m_gui)
m_gui.mainloop()

Resources