GPIOZero Pi Alarm system: Need alternative for time delay - python

I require help with my alarm system code that I am building with GPIOZero library (my boss really likes the library). The system is meant for a laptop cart so people do not forget to close the door as there is a buzzer that rings if people leave it open for too long. So what I want it to do is really simple but it always waits for the function to finish before going to the next:
If door opens: Log time, Time Delay ,Buzzer turns on
If door is closed/closes: Buzzer is off, Log Time
Is there a way to not wait for the function to finish in the GPIOZero library. Please let me know!
from gpiozero import Button
from signal import pause
from gpiozero import Buzzer
from gpiozero import LED
from time import sleep
##### VARIABLES DEF #######
button = Button(21, pull_up=True)
buzzer = Buzzer(4)
def door_opened():
f = open("log" + '.txt', 'a')
f.write("Opened " + time.strftime("%m-%d-%Y %X"))
f.close()
print("Door Open")
sleep(100)
print("door held, alarm on")
buzzer.on
def door_closed():
f = open("log" + '.txt', 'a')
f.write(" " + time.strftime("%X"))
f.write('\n')
f.close()
print("Door Closed")
buzzer.off()
print("Buzzer off")
button.when_pressed = door_closed
button.when_released = door_opened
pause()

You will have to restructure things a little to get this done.
The idea here is that you have a pair of global variables that are updated by the GPIOZero event handlers:
door_is_open – true when the door is open
door_open_counter – counts the seconds the door has been opened (this is actually only reset to zero by the event handlers).
There's also an infinite main loop that ticks once per second which reads the door_is_open variable and increments the open counter... and finally, if the open counter reaches the threshold desired (5 seconds here because we're impatient people), it turns the buzzer on.
The door_closed handler takes care of resetting the buzzer, but that could also be done in the main loop.
I also took the liberty of refactoring the logging into a function of its own that handily also prints the same thing to the console. (As an aside, I'd suggest using ISO 8601 date formatting, but I didn't want to impose that here.)
A further refactoring might wrap this all in a neat class to avoid global variables.
(This all is dry-coded, so your mileage may vary, but the idea should work. :) )
import time
from gpiozero import Button, Buzzer
button = Button(21, pull_up=True)
buzzer = Buzzer(4)
door_is_open = False
door_open_counter = 0
def log_event(text):
print(log_line)
with open("log.txt", "a") as log_file:
log_line = "%s | %s" % (time.strftime("%m-%d-%Y %X"), text)
log_file.write(log_line + "\n")
def door_opened():
global door_is_open
global door_open_counter
log_event("Opened")
door_is_open = True
door_open_counter = 0
def door_closed():
global door_is_open
global door_open_counter
log_event("Closed")
buzzer.off()
door_is_open = False
door_open_counter = 0
button.when_pressed = door_closed
button.when_released = door_opened
while True:
time.sleep(1)
if door_is_open:
door_open_counter += 1
if door_open_counter >= 5:
buzzer.on()

Related

How to get out of a while loop with a specific key

I am trying to make an auto clicker but when i try to make my code exit it doesnt
here is my code
import mouse
import keyboard
import time
import os
os.system('cls')
def Config():
print("Click every")
hour = int(input("Hour: "))
minute = int(input("Minute: "))
second = int(input("Second: "))
total_time = hour*3600 + minute*60 + second
print("f6 to start and f10 to stop")
keyboard.wait('f6')
while True:
time.sleep(total_time)
mouse.click()
#def Fastest():
print(" Auto clicker!!!")
print(" By ze")
print("-------------------------")
print("Auto click on desired time or Fastest?")
choose = int(input("""
1. Config (No milliseconds)
2. Fastest
"""))
if choose == 1:
Config()
# elif choose == 2:
# Fastest()
#TODO:
# use mouse.click
# make it click with time
# make function fastest start with f1 and stops with f2
# create back up file
i tried an if statement with keyboard.is_pressed('key') thinking it would work but it doesnt my results are that the code exits (if key is pressed then exit)
You need to check if the key is pressed in your infinite loop. If it is pressed, you need to exit
while True:
time.sleep(total_time)
mouse.click()
if keyboard.is_pressed("f10"):
break
But this waits for the sleep function , so you'll need to hold f10 for total_time seconds.
You should use a loop to check for the key, rather than sleeping
import datetime
...
clicking = True
while clicking:
mouse.click()
s = datetime.datetime.now()
while ((datetime.datetime.now()-s).total_seconds() < total_time):
# This runs while the difference in time since you started the loop is less than the time you want to wait
if keyboard.is_pressed("f10"):
clicking = False
break
Use one thread to handle user input and another thread to do the clicking:
from threading import Thread
from msvcrt import getwch
import mouse
done = False
def auto_click():
print("Press \"q\" to quit")
global done
while True:
if getwch() == "q":
done = True
break
Thread( target = auto_click, daemon = True ).start()
while not done: # you can add whatever logic you want including a time gate here
mouse.click()

Is there anyway to wait for a couple of minutes and if no input is provided then take the value "n" as input for first variable?

while 1:
wat=water()
if wat==10:
print("water condition")
mixer.music.load("water.mp3")
mixer.music.play()
first=input("Drank?Y/N")
if first.lower()=="y":
with open("HealthLog.txt","a") as water1:
Content=f"Drank water at [{getdate()}] \n"
water1.write(Content)
else:
pass
Is there any way to wait for a couple of minutes and if no input is provided, then take the value "n" as input for the first variable?
Guess by default it will wait indefinitely. I tried using a timer function, but it cannot record any input.
What I am trying to do is to track my activities, so if I drink water I say y--> this records my activity and writes it to a file.
All help will be greatly appreciated
Here is how you can use a combination of pyautogui.typewrite, threading.Thread and time.sleep:
from pyautogui import typewrite
from threading import Thread
from time import sleep
a = ''
def t():
sleep(5)
if not a: # If by 5 seconds a still equals to '', as in, the user haven't overwritten the original yet
typewrite('n')
typewrite(['enter'])
T = Thread(target=t)
T.start()
a = input()
b = input() # Test it on b, nothing will happen
Here is the code implemented into your code:
from pyautogui import typewrite
from threading import Thread
from time import sleep
while 1:
wat = water()
if wat == 10:
print("water condition")
mixer.music.load("water.mp3")
mixer.music.play()
first = 'waiting...'
def t():
sleep(5)
if first == 'waiting...':
typewrite('n')
typewrite(['enter'])
T = Thread(target=t)
T.start()
first = input("Drank?Y/N")
if first.lower() == "y":
with open("HealthLog.txt","a") as water1:
Content=f"Drank water at [{getdate()}] \n"
water1.write(Content)
else:
pass

Python - Pynput key press seems to not be the same as an actual key press

I am trying to code a very simple afk-farm-bot for a game. All it needs to do is pressing Space two times every few seconds. This works in writing software, such as the normal text editor, but it doesn't work in the game. There has to be a difference between pynput keyboard presses and normal ones, and I need to know what that difference is, in order to make my code work. Can you help me?
Here is the entire code I am using:
from pynput.keyboard import Key, Controller
import time
import random
keyboard = Controller()
print("NosTale-Bot started. Please Insert specifications:")
idelay = int(input("Initial Delay (ins sec): "))
tickGap = float(input("Tick Gap (in sec): "))
tickDivergence = float(input("Tick Divergence (in sec): "))
maxDuration = float(input("Max Duration (in min): "))
print("You have " + str(idelay) + " seconds to switch to NosTale.")
time.sleep(idelay)
sTime = time.time()
wTime = time.time()
rDivergence = -tickGap
tCounter = 0
while True:
cTime = time.time() - wTime
if (cTime >= tickGap + rDivergence):
tCounter += 1
print("Tick " + str(tCounter) + ": " + str(time.time() - sTime))
keyboard.press(Key.space)
keyboard.release(Key.space)
time.sleep(0.05)
keyboard.press(Key.space)
keyboard.release(Key.space)
rDivergence = (random.random()*tickDivergence*2-tickDivergence)
wTime = time.time()
if (time.time() - sTime >= maxDuration*60):
break
Yes, it's different.
It seems that pynput uses keybd_event function to generate a WM_KEYUP or WM_KEYDOWN message.
The keyboard input can come from the local keyboard driver or from calls to the keybd_event function. If the input comes from a call to keybd_event, the input was "injected".
When a keyboard hook is set, the system callback will return a KBDLLHOOKSTRUCT structure. The 4th bit of the 'flag' field :
Specifies whether the event was injected. The value is 1 if that is the case; otherwise, it is 0. Note that bit 1 is not necessarily set when bit 4 is set.
The listener of the pynput itself can catch the difference. With the win32_event_filter callback:
def win32_event_filter(msg, data):
if data.flags & 0x10:
return False
return True
with Listener(
on_press=on_press,
on_release=on_release,
win32_event_filter = win32_event_filter) as listener:
listener.join()
With win_32_event_filter callback, the pynput can distinguish the key events generated by its controller from the key events generated by a true keyboard, then the listener can shield them.
Games can do the same things, of course.
Unfortunately,pynput module couldn't do its work in most game.It uses a junior listener thread.It seems it couldn't work in most game.
So to handle this problem,you can use AutoHotKey(A very useful tool,and easy to learn) to do that.My pynput script couldn't run in Overwatch.So I used to use AutoHotKey to control Overwatch successfully.

Raspberry Pi Python pause a loop sequence, when button pushed

I have a raspberry PI 2. With a relay board, what i use to for a switch sequence (like a traffic light).
I use a tool, called "webiopi" what create buttons on a website. When the button is clicked the function of the python script below is started.
What i want is to break out of the loop (or pause it) when another button is clicked. However, as long this loop is running, the tool don't look at the webpage
A kind of similar question is asked here Exiting a continuous loop in python via webiopi but this is for a single event and the solution doesn't work in my case.
Question is. How can I make this script look at a button what is clicked (can be a gpio switch as well) while the loop is running
GPIO_nek=11
GPIO_schouder=12
GPIO_rug1=8
GPIO_ONOFF=18
interval1 = 2
interval2 = 4
for x in range(0, 20):
GPIO.digitalWrite(GPIO_nek, GPIO.LOW)
time.sleep(interval1)
GPIO.digitalWrite(GPIO_schouder, GPIO.LOW)
time.sleep(interval1)
GPIO.digitalWrite(GPIO_nek, GPIO.HIGH)
time.sleep(interval1)
GPIO.digitalWrite(GPIO_rug1, GPIO.LOW)
time.sleep(interval2)
GPIO.digitalWrite(GPIO_schouder, GPIO.HIGH)
if (GPIO.digitalRead(GPIO_ONOFF) == GPIO.LOW):
GPIO.digitalWrite(GPIO_ONOFF, GPIO.HIGH)
break
When monitoring a real time event such as sensors or your button your best solution will be setting up a separate thread or process that contains nothing but an infinite loop that watches the resource and sets a flag when something interesting happens.
The example below sets up a process that automatically takes a picture on the RPI aprox. every minute.
#!/usr/bin/python
#Threading Prototype - Running a background thread that will take
# pictures at the appropriate time
#------------------------------------------------------------------
from multiprocessing import Queue
from multiprocessing import Process
from time import sleep
from datetime import datetime
from datetime import timedelta
from subprocess import call
#Doing the work ---------------------------------------------------
#Global Variables ---
messages = Queue()
start_time = datetime.now()
#Returns number of Milliseconds since start of program
def milliSinceStart():
global start_time
dt = datetime.now() - start_time
ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0
return ms
#Process Methods --------------------------------------------------
def processMessages():
print "Message Processor Launched"
while True:
print messages.get() #should halt until message in queue
sleep(0.1) #sleep for a tick just to avoid a run away process
def processPicutres():
print "Picture Taker Launched"
pictureCycleStart = milliSinceStart()
index = 0
while True:
if milliSinceStart() - pictureCycleStart > 10000: #once a minute
a = "blip" + str(index) + ".jpg"
b = "raspistill -n -t 100 -o " + a
messages.put("Click")
call ([b], shell=True)
messages.put("picture taken - " + b)
index = index + 1
pictureCycleStart = milliSinceStart()
sleep(0.1) #wait a tick -- don't hog processor time
def main():
print "MultiProcessing Prototype"
print "Launching message process"
Process(target=processMessages).start()
print "Back from launch"
print "Launching picture taking process"
Process(target=processPicutres).start()
print "Back from launch"
cycleStart = milliSinceStart()
index = 0
while True:
if milliSinceStart() - cycleStart > 1000:
messages.put("Tick " + str(index))
cycleStart = milliSinceStart()
index = index + 1
if __name__ == "__main__":
main()
The main method launches the Messaging and Picture processes and then sets up its own little infinite loop that does nothing more that display the message "Tick" every second. The picture process sets up a separate infinite loop, watching the clock and taking a picture periodically. The Message process monitors the picture process (again, an infinite loop) and when it detects that a picture has been taken, it outputs the fact to the screen.
The important part of this for your purpose is the message queue. The process queue is what is allowing the Picture and Message processes to communicate.
And because the task take place in different processes, it matters not if one process pauses as the others are always active. If you set up a button monitor process you can be checking message queue for this fact and halting your main program when the button is pressed. This pause in the main program would not effect the button process which could then pick up on the fact that the button is pressed again.
If the button is the GPIO switch as you mentioned at the end of the question, instead of the webpage button, then you can make use of an inbuilt GPIO interrupt function that saves your computer the resouces of constant polling:
import RPi.GPIO as GPIO
from threading import Event # We'll use it like time.sleep, but we can interrupt it.
GPIO_nek=11
GPIO_schouder=12
GPIO_rug1=8
GPIO_ONOFF=18
interval1 = 2
interval2 = 4
GPIO.setup(GPIO_ONOFF, GPIO.IN, pull_up_down=GPIO.PUD_UP)
done = False # loop control
timer = Event()
def quit_loop(): # Called by inbuilt threaded interrupt
global done
done = True
timer.set() # Interrupt the waiting
GPIO.add_event_detect(GPIO_ONOFF, GPIO.FALLING, callback=quit_loop, bouncetime=300) # Setup interrupt to call quit_loop
Because you're using this to break out of a loop, you want to shorten that loop to a single process:
tasks = [
(GPIO_nek, GPIO.LOW, interval1),
(GPIO_schouder, GPIO.LOW, interval1),
(GPIO_nek, GPIO.HIGH, interval1),
(GPIO_rug1, GPIO.LOW, interval2),
(GPIO_schouder, GPIO.HIGH, 0) ]
for pin, level, interval in tasks * 20: # Above you ran it 20 times, this notation keeps it in a single loop to break our o
if not done:
GPIO.digitalWrite(pin, level)
timer.wait(interval)
else:
timer.clear()
break
By using the threading Event().wait() and .set() instead of the standard time.sleep() you won't even have to wait for the sleep interval to finish.

How to set time limit on raw_input

in python, is there a way to, while waiting for a user input, count time so that after, say 30 seconds, the raw_input() function is automatically skipped?
The signal.alarm function, on which #jer's recommended solution is based, is unfortunately Unix-only. If you need a cross-platform or Windows-specific solution, you can base it on threading.Timer instead, using thread.interrupt_main to send a KeyboardInterrupt to the main thread from the timer thread. I.e.:
import thread
import threading
def raw_input_with_timeout(prompt, timeout=30.0):
print(prompt, end=' ')
timer = threading.Timer(timeout, thread.interrupt_main)
astring = None
try:
timer.start()
astring = input(prompt)
except KeyboardInterrupt:
pass
timer.cancel()
return astring
this will return None whether the 30 seconds time out or the user explicitly decides to hit control-C to give up on inputting anything, but it seems OK to treat the two cases in the same way (if you need to distinguish, you could use for the timer a function of your own that, before interrupting the main thread, records somewhere the fact that a timeout has happened, and in your handler for KeyboardInterrupt access that "somewhere" to discriminate which of the two cases occurred).
Edit: I could have sworn this was working but I must have been wrong -- the code above omits the obviously-needed timer.start(), and even with it I can't make it work any more. select.select would be the obvious other thing to try but it won't work on a "normal file" (including stdin) in Windows -- in Unix it works on all files, in Windows, only on sockets.
So I don't know how to do a cross-platform "raw input with timeout". A windows-specific one can be constructed with a tight loop polling msvcrt.kbhit, performing a msvcrt.getche (and checking if it's a return to indicate the output's done, in which case it breaks out of the loop, otherwise accumulates and keeps waiting) and checking the time to time out if needed. I cannot test because I have no Windows machine (they're all Macs and Linux ones), but here the untested code I would suggest:
import msvcrt
import time
def raw_input_with_timeout(prompt, timeout=30.0):
print(prompt, end=' ')
finishat = time.time() + timeout
result = []
while True:
if msvcrt.kbhit():
result.append(msvcrt.getche())
if result[-1] == '\r': # or \n, whatever Win returns;-)
return ''.join(result)
time.sleep(0.1) # just to yield to other processes/threads
else:
if time.time() > finishat:
return None
The OP in a comment says he does not want to return None upon timeout, but what's the alternative? Raising an exception? Returning a different default value? Whatever alternative he wants he can clearly put it in place of my return None;-).
If you don't want to time out just because the user is typing slowly (as opposed to, not typing at all!-), you could recompute finishat after every successful character input.
I found a solution to this problem in a blog post. Here's the code from that blog post:
import signal
class AlarmException(Exception):
pass
def alarmHandler(signum, frame):
raise AlarmException
def nonBlockingRawInput(prompt='', timeout=20):
signal.signal(signal.SIGALRM, alarmHandler)
signal.alarm(timeout)
try:
text = raw_input(prompt)
signal.alarm(0)
return text
except AlarmException:
print '\nPrompt timeout. Continuing...'
signal.signal(signal.SIGALRM, signal.SIG_IGN)
return ''
Please note: this code will only work on *nix OSs.
The input() function is designed to wait for the user to enter something (at least the [Enter] key).
If you are not dead set to use input(), below is a much lighter solution using tkinter. In tkinter, dialog boxes (and any widget) can be destroyed after a given time.
Here is an example :
import tkinter as tk
def W_Input (label='Input dialog box', timeout=5000):
w = tk.Tk()
w.title(label)
W_Input.data=''
wFrame = tk.Frame(w, background="light yellow", padx=20, pady=20)
wFrame.pack()
wEntryBox = tk.Entry(wFrame, background="white", width=100)
wEntryBox.focus_force()
wEntryBox.pack()
def fin():
W_Input.data = str(wEntryBox.get())
w.destroy()
wSubmitButton = tk.Button(w, text='OK', command=fin, default='active')
wSubmitButton.pack()
# --- optionnal extra code in order to have a stroke on "Return" equivalent to a mouse click on the OK button
def fin_R(event): fin()
w.bind("<Return>", fin_R)
# --- END extra code ---
w.after(timeout, w.destroy) # This is the KEY INSTRUCTION that destroys the dialog box after the given timeout in millisecondsd
w.mainloop()
W_Input() # can be called with 2 parameter, the window title (string), and the timeout duration in miliseconds
if W_Input.data : print('\nYou entered this : ', W_Input.data, end=2*'\n')
else : print('\nNothing was entered \n')
from threading import Timer
def input_with_timeout(x):
def time_up():
answer= None
print('time up...')
t = Timer(x,time_up) # x is amount of time in seconds
t.start()
try:
answer = input("enter answer : ")
except Exception:
print('pass\n')
answer = None
if answer != True: # it means if variable have somthing
t.cancel() # time_up will not execute(so, no skip)
input_with_timeout(5) # try this for five seconds
As it is self defined... run it in command line prompt , I hope you will get the answer
read this python doc you will be crystal clear what just happened in this code!!
A curses example which takes for a timed math test
#!/usr/bin/env python3
import curses
import curses.ascii
import time
#stdscr = curses.initscr() - Using curses.wrapper instead
def main(stdscr):
hd = 100 #Timeout in tenths of a second
answer = ''
stdscr.addstr('5+3=') #Your prompt text
s = time.time() #Timing function to show that solution is working properly
while True:
#curses.echo(False)
curses.halfdelay(hd)
start = time.time()
c = stdscr.getch()
if c == curses.ascii.NL: #Enter Press
break
elif c == -1: #Return on timer complete
break
elif c == curses.ascii.DEL: #Backspace key for corrections. Could add additional hooks for cursor movement
answer = answer[:-1]
y, x = curses.getsyx()
stdscr.delch(y, x-1)
elif curses.ascii.isdigit(c): #Filter because I only wanted digits accepted
answer += chr(c)
stdscr.addstr(chr(c))
hd -= int((time.time() - start) * 10) #Sets the new time on getch based on the time already used
stdscr.addstr('\n')
stdscr.addstr('Elapsed Time: %i\n'%(time.time() - s))
stdscr.addstr('This is the answer: %s\n'%answer)
#stdscr.refresh() ##implied with the call to getch
stdscr.addstr('Press any key to exit...')
curses.wrapper(main)
under linux one could use curses and getch function, its non blocking.
see getch()
https://docs.python.org/2/library/curses.html
function that waits for keyboard input for x seconds (you have to initialize a curses window (win1) first!
import time
def tastaturabfrage():
inittime = int(time.time()) # time now
waitingtime = 2.00 # time to wait in seconds
while inittime+waitingtime>int(time.time()):
key = win1.getch() #check if keyboard entry or screen resize
if key == curses.KEY_RESIZE:
empty()
resize()
key=0
if key == 118:
p(4,'KEY V Pressed')
yourfunction();
if key == 107:
p(4,'KEY K Pressed')
yourfunction();
if key == 99:
p(4,'KEY c Pressed')
yourfunction();
if key == 120:
p(4,'KEY x Pressed')
yourfunction();
else:
yourfunction
key=0
This is for newer python versions, but I believe it will still answer the question. What this does is it creates a message to the user that the time is up, then ends the code. I'm sure there's a way to make it skip the input rather than completely end the code, but either way, this should at least help...
import sys
import time
from threading import Thread
import pyautogui as pag
#imports the needed modules
xyz = 1 #for a reference call
choice1 = None #sets the starting status
def check():
time.sleep(15)#the time limit set on the message
global xyz
if choice1 != None: # if choice1 has input in it, than the time will not expire
return
if xyz == 1: # if no input has been made within the time limit, then this message
# will display
pag.confirm(text = 'Time is up!', title = 'Time is up!!!!!!!!!')
sys.exit()
Thread(target = check).start()#starts the timer
choice1 = input("Please Enter your choice: ")

Categories