Raspberry PI Button Push Listener - python

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

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.

how do I loop servo motor and stop and loop again?

I bought mg995 servo motor but it seems controlling is different from the mg90 because it is digital.
What I want to do is spinning mg995 loop, stops when I press ctrl+c and starts loop again when I press the enter. the code is below but it won't loop again after servo1.stop()
from cmath import inf
from PIL import Image
import numpy as np
import RPi.GPIO as GPIO
from time import sleep
################# -----------------------------
GPIO.setmode(GPIO.BCM)
servo1_pin = 18
GPIO.setup(servo1_pin, GPIO.OUT)
servo1 = GPIO.PWM(servo1_pin, 50)
servo1.start(0)
servo_min_duty = 3
servo_max_duty = 12
def set_servo_degree(servo_num, degree):
if degree > 180:
degree = 180
elif degree < 0:
degree = 0
duty = servo_min_duty+(degree*(servo_max_duty-servo_min_duty)/180.0)
if servo_num == 1:
servo1.ChangeDutyCycle(duty)
#################-------------------------------
try:
while True:
set_servo_degree(1, 0)
except KeyboardInterrupt:
servo1.stop() # after servo stops, it won't start again.
print("should be end")
pass
go_button = input('go? press enter!')
if go_button == "":
## after press the enter, starts loop again.
try:
while True:
set_servo_degree(1, 0)
else:
break()
sleep(2)
GPIO.cleanup()
thank you!
The while loop should be outside of the set_servo_degree function.
The modified part of your code is as below
servo_num = 1
servo_deg = 0
while True:
try:
set_servo_degree(servo_num, servo_deg)
except KeyboardInterrupt:
servo1.stop()
go_button = input('go? press enter!')
if go_button == "":
set_servo_degree(servo_num, servo_deg)
else:
break
sleep(2)
GPIO.cleanup()

Two-Button Menu Iteration

I've got a script that I'm adapting to micropython on a 2040, and I want to use two buttons to navigate the menu structure. I can't figure out how to make the iterate loop in the multi-choice menus work right... here's what I've got so far:
""" fit: a productivity logger """
import time
import sys
import os
import uhashlib
import machine
def final_print(sec,final_hash,final_survey):
""" leaves the summary on the screen before shutting down """
mins = sec // 60
sec = sec % 60
hours = mins // 60
mins = mins % 60
short_sec = int(sec)
duration = (str(hours) + "/" + str(mins) + "/" + str(short_sec))
print("> fit the",str(final_hash)," went ",str(final_survey)," lasted //",str(duration))
def timer_down(f_seconds,timer_focus):
""" counts down for defined period """
now = time.time()
end = now + f_seconds
while now < end:
now = time.time()
fit_progress(now,end,timer_focus,f_seconds)
b1pressed = button1.value()
time.sleep(0.01)
if not b1pressed:
print('Ended Manually!')
break
def timer_up(timer_focus):
""" counts up for indefinite period """
now = time.time()
while True:
minutes = int((time.time() - now) / 60)
print(str(timer_focus)," for ",str(minutes))
b1pressed = button1.value()
time.sleep(0.01)
if not b1pressed:
print('Ended Manually!')
break
def fit_progress(now,end,timer_focus,f_seconds):
""" tracks progress of a count-down fit and prints to screen """
remain = end - now
f_minutes = int((remain)/60)
j = 1 - (remain / f_seconds)
pct = int(100*j)
print(str(timer_focus),str(f_minutes),str(pct))
def multi_choice(options):
done = 0
while done == 0:
for i in options:
b1pressed = button1.value()
b2pressed = button2.value()
time.sleep(.01)
b1released = button1.value()
b2released = button2.value()
if b2pressed and not b1pressed:
print(i," b2 pressed")
continue
if b1pressed and not b2pressed:
print(i," b1 pressed")
time.sleep(2)
return i
button1 = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
button2 = machine.Pin(3, machine.Pin.IN, machine.Pin.PULL_UP)
print("format?")
fType = multi_choice(['30-minute fit','60-minute fit','indefinite fit'])
print(fType," selected")
print("focus?")
F_FOCUS = multi_choice(['personal fit','work fit','learn fit','admin fit'])
print(F_FOCUS," selected")
fStart = time.time()
if fType == "30-minute fit":
timer_down(1800,F_FOCUS)
elif fType == "60-minute fit":
timer_down(3600,F_FOCUS)
elif fType == "indefinite fit":
timer_up(F_FOCUS)
else:
sys.exit()
fEnd = time.time()
print("sentiment?")
F_SURVEY = multi_choice(['+','=','-'])
print(F_SURVEY," selected")
fDuration = fEnd - fStart
F_HASH = uhashlib.sha256(str(fEnd).encode('utf-8')).digest()
F_HASH_SHORT = F_HASH[0:3]
fitdb = open("data.csv","a")
fitdb.write(str(F_HASH)+","+str(fType)+","+str(F_FOCUS)+","+str(F_SURVEY)+","+str(fStart)+","+str(fEnd)+","+str(fDuration)+"\n")
fitdb.close()
final_print(fDuration,F_HASH_SHORT,F_SURVEY)
print(F_HASH_SHORT," ",F_HASH)
In particular, this is the logic I'm wrangling with:
def multi_choice(options):
done = 0
while done == 0:
for i in options:
b1pressed = button1.value()
b2pressed = button2.value()
time.sleep(.01)
b1released = button1.value()
b2released = button2.value()
if b2pressed and not b1pressed:
print(i," b2 pressed")
continue
if b1pressed and not b2pressed:
print(i," b1 pressed")
time.sleep(2)
return i
Here's what I'm wanting to do:
For each item in the set,
Display the item, wait for button press
If button 1 is pressed, select that item, return the item.
If button 2 is pressed, display the next item, wait for button press.
(iterate until button 1 pressed to select item)
Again, this is micropython, so I don't have all the modules you'd think available... woudl be best to do this in raw code.
0.01 second is way too short. The key is, after you detect "button down", you need to wait for "button up". You need something like:
def wait_for_btn_up(btn):
count = 2
while count > 0:
if btn.value();
count = 2
else:
count -= 1
time.sleep(0.01)
def multi_choice(options):
for i in options:
print( "trying", i )
# Wait for any button press.
while 1:
b1pressed = button1.value()
b2pressed = button2.value()
if b1pressed or b2pressed:
break
if b1pressed:
print( i, "chosen" )
wait_for_btn_up(button1)
return i
# We know B2 was pressed.
wait_for_btn_up(button2)

Detect key pressed in python not working

import pyautogui, time, threading, keyboard, msvcrt
global active, exitp
active = False
exitp = False
Names = []
def mainLoop():
global active, exitp
pressedkey = msvcrt.getch()
while True:
if pressedkey == 'z':
active = not active
elif pressedkey == 'x':
exitp = False
break
def running():
global exitp
while not exitp:
print("Running")
time.sleep(3)
start = time.time()
print("Your screen size is: " + str(pyautogui.size()))
width, height = pyautogui.size()
t1 = threading.Thread(target=mainLoop, args=())
t2 = threading.Thread(target=running, args=())
t1.start()
t2.start()
while not exitp:
if active:
pyautogui.click(90, height - 110)
for i in range(len(Names)):
if active:
pyautogui.typewrite(Names)
pyautogui.press("enter")
else:
break
active = False
end = time.time()
print("Execution time: " + str(end-start) + " seconds")
trying to make a loop that exits when i press 'x', and types/stops typing names from my array called "Names" when pressing 'z', however, i'm pressing both 'x' and 'z' but they don't anything, help?
from pynput import keyboard
import pyautogui
import threading
import sys
class Status:
_exit = False
active = True
names = [str(i) for i in range(0, 10, 1)]
width, height = pyautogui.size()
def mainThread():
listenerThread = keyboard.Listener(on_press = onKeyPress)
listenerThread.start()
while(not Status._exit):
if (not Status.active):
continue
pyautogui.click(90, height - 110)
for i in range(len(names)):
if (not Status.active or Status._exit):
break
pyautogui.typewrite(names[i])
pyautogui.press("enter")
keyboard.Listener.stop(listenerThread)
print("Stopped listerning")
sys.exit(0)
def onKeyPress(key):
print("Still listerning")
try:
k = key.char
except Exception:
k = key.name
if (k == "z"):
Status.active = not Status.active
elif (k == "x"):
Status._exit = not Status._exit
#controlThread = threading.Thread(target = mainThread)
#controlThread.start()
mainThread()
Instead of using globals I used a 'static' class which stored the status. I also used a different module to listen to key inputs.
I think this is what you wanted.
EDIT: You don't have to have the main loop as a thread. If you wanted to do other things then set it as a thread (uncomment the two lines and comment the last line) or just leave it how it is.

how to stop timer at 20 minutes

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

Categories