UPDATE 2: How Do I Stop a Function in a while loop? - python-3.x

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()

Related

Python 3 Subprocess does not terminate with signal.SIGINT

I've read many of the questions here on SO, but none of the solutions seem to solve my problem. I have a file called "light_led.py" which looks like this
import RPi.GPIO as gpio
import time
# Set the mode for the GPIO numbering
gpio.setmode(gpio.BCM)
pin = 23
# Set the GPIO as output
gpio.setup(pin, gpio.OUT, initial=gpio.LOW)
try:
while True: # Run forever
gpio.output(pin, gpio.HIGH) # Turn on
time.sleep(1) # Sleep for 1 second
gpio.output(pin, gpio.LOW) # Turn off
time.sleep(1) # Sleep for 1 second
except KeyboardInterrupt:
# cleanup all GPIO
gpio.cleanup()
print('Cleaned up')
It simply toggles an LED on and off. This file is called in the following script
import RPi.GPIO as gpio
import time
import subprocess
import signal
# Set the mode for the GPIO numbering
gpio.setmode(gpio.BCM)
pin = 24
# Set the GPIO as input w/ pull up
gpio.setup(pin, gpio.IN, pull_up_down = gpio.PUD_UP)
# Initialize
pin_prev = gpio.input(pin)
while True:
pin = gpio.input(pin)
if (pin == 0) and (pin_prev == 1):
process = subprocess.Popen('python <PATH-TO-FILE>/light_led.py', shell=True)
print('Should be blinking')
if (pin == 1) and (pin_prev == 0):
process.send_signal(signal.SIGINT)
print('Should be off')
# Update previous state
pin_prev = pin
# Slight pause to debounce
time.sleep(0.05)
The files run fine and do what they're suppose to. The only problem I'm having is that I can't stop the blinking light by "flipping the switch". In other words, I'm having trouble with this process.send_signal(signal.SIGINT). There are not errors, so I don't have much to report.
I've tried process.terminate() and os.kill(process.pid, signal.SIGINT), both with no luck. I just want to stop the subprocess from running and continue with the main toggle script. From what I read, "signal.SIGINT" should invoke the "KeyboardInterrupt" in the subprocess...but maybe not?

Python tkinter- Threading through button click

I am displaying a sensor device's measurement values using the Tkinter GUI application. The sensor sends new data every second. I started a new thread and put the result in a Queue and process the queue to display the values on GUI. Now I am facing another problem. The sensor has two modes of operations. For simplicity, I have used a random generator in the program. Users should be able to switch the modes using two buttons. Button-1 for Mode-1, Button-2 for Mode-2. (say the mode-1 operation is random.randrange(0,10) and mode-2 operation is random.randrange(100, 200). How do I control these two operations through Threading? if a user started a mode-1 operation, when he presses the Button-2, the mode-1 operation should stop (thread-1) and mode-2 operation (thread-2) should start. Does it mean do I need to kill thread-1? Or is there any way to control two modes in same thread? I am totally new into threading. Any suggestions, please.
import tkinter
import threading
import queue
import time
import random
class GuiGenerator:
def __init__(self, master, queue):
self.queue = queue
# Set up the GUI
master.geometry('800x480')
self.output = tkinter.StringVar()
output_label = tkinter.Label(master, textvariable= self.output)
output_label.place(x=300, y=200)
#I haven't shown command parts in following buttons. No idea how to use it to witch modes?
mode_1_Button = tkinter.Button(master, text = "Mode-1")
mode_1_Button.place(x=600, y=300)
mode_2_Button = tkinter.Button(master, text = "Mode-2")
mode_2_Button.place(x=600, y=400)
def processQueue(self):
while self.queue.qsize():
try:
sensorOutput = self.queue.get() #Q value
self.output.set(sensorOutput) #Display Q value on GUI
except queue.Empty:
pass
class ClientClass:
def __init__(self, master):
self.master = master
# Create the queue
self.queue = queue.Queue()
# Set up the GUI part
self.myGui = GuiGenerator(master, self.queue)
#How do I switch the modes of operations? do I need some flags setting through button press?
# Set up the thread to do asynchronous I/O
self.thread1_mode1 = threading.Thread(target=self.firstModeOperation)
self.thread1_mode1.start()
# Start the periodic call in the GUI to check if the queue contains
# anything new
self.periodicCall()
def periodicCall(self):
# Check every 1000 ms if there is something new in the queue.
self.myGui.processQueue()
self.master.after(1000, self.periodicCall)
def firstModeOperation(self):
while True: #??? how do i control here through mode selection
time.sleep(1.0)
msg_mode_1= random.randrange(0,10)
self.queue.put(msg_mode_1)
def secondModeOperation(self):
while True: #??? how do i control here through mode selection
time.sleep(1.0)
msg_mode_2= random.randrange(100,200)
self.queue.put(msg_mode_2)
#Operation part
root = tkinter.Tk()
client = ClientClass(root)
root.mainloop()

RaspberryPi Stopwatch (TKinter) with Trigger

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.

How to create GUI objects one by one with Tkinter

I like to create an object per second and make the show up one by one. However, the code below wait for 3 seconds and show them all at the same time.
from tkinter import *
import time
def create():
for i in range(3):
r4=Radiobutton(root, text="Option 1"+str(i), value=1)
r4.pack( anchor = W )
time.sleep(1)
root = Tk()
create()
root.mainloop()
Your code, as is, creates a one object per second as you desired it, but this objects need to be shown, and they're shown when code flow reaches the mainloop. Hence, for observer, it looks like there're no objects at all after one second.
Of course, you can use sleep and update, but beware - sleeping leads to unresponsive window, so it's OK option (to be honest - not OK at all), if your application isn't drawn and you're outside of mainloop, but if it's not - prepare for a "frozen" window, because GUI can redraw himself only in mainloop (an event loop, you can reach it with update as well) and sleep blocks this behaviour.
But there's a good alternative, the after method, take a look on it!
And there's a snippet, so you can see the difference:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
import time
def create_with_sleep():
for _ in range(3):
tk.Radiobutton(frame_for_sleep, text="Sleep Option").pack(anchor='w')
time.sleep(int(time_entry.get()))
root.update()
def create_with_after(times=3):
if times != 0:
tk.Radiobutton(frame_for_after, text="After Option").pack(anchor='w')
times -= 1
root.after(int(time_entry.get()) * 1000, lambda: create_with_after(times))
root = tk.Tk()
test_yard_frame = tk.Frame(root)
frame_for_sleep = tk.Frame(test_yard_frame)
frame_for_after = tk.Frame(test_yard_frame)
test_yard_frame.pack()
frame_for_sleep.pack(side='left')
frame_for_after.pack(side='left')
button_frame = tk.Frame(root)
button_for_sleep = tk.Button(button_frame, text='Create 3 radiobuttons with sleep+update', command=create_with_sleep)
button_for_after = tk.Button(button_frame, text='Create 3 radiobuttons with after', command=create_with_after)
button_frame.pack()
button_for_sleep.pack(side='left')
button_for_after.pack(side='left')
time_label = tk.Label(root, text='Time delay in seconds:')
time_label.pack(fill='x')
time_entry = tk.Entry(root)
time_entry.insert(0, 1)
time_entry.pack(fill='x')
root.mainloop()
With 1 seconds delay there's no much difference, but you can try to increase delay to understand why after option is preferable in general.
You can use update to call a refresh on your objects.
In use, you would have to add the line root.update() in your for loop.

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