I have a count-up/count-down timer library, and I've written some demo code that kicks off an instance of its main class, and updates the console as it counts down. When the timer expires, it displays a message to that effect. The demo code is pretty rudimentary and non-interactive, however, and I would like to improve it.
I would like to add the ability to pause/resume/reset the timer by pressing keys on the keyboard. The user would press the spacebar to pause/resume, and another key (maybe "r") to reset the timer. The console would be continually updated and display the current "remaining" time, including freezing the time when the timer is paused.
What I am struggling with is what approach would be best to use here. I have some limited experience with threads, but no experience with async. For now, I am not interested in using a full blown TUI, either, even though that might give the most satisfying results...I am treating this as a learning experience.
My first inclination was to use threads, one each for the user input and the timer countdown / console update tasks; but how do I get messages from the console when it receives user input (e.g. "pause") to the other task so that the time pauses?
I want to do this in the most Pythonic way I can - any suggestions?
I ended up using async and learning a bit in the process. Here's the code. And BTW it is a lot lighter weight than my threaded verssion. My Macbook pro fans spin up to max speed when I run the threaded version. But I can barely hear them at all with the async version.
import sys
import asyncio
from count_timer import CountTimer
from blessed import Terminal
def count():
if counter.remaining > 10:
print(
term.bold
+ term.green
+ term.move_x(0)
+ term.move_up
+ term.clear_eol
+ str(round(counter.remaining, 3))
)
elif counter.remaining > 5:
print(
term.bold
+ term.yellow2
+ term.move_x(0)
+ term.move_up
+ term.clear_eol
+ str(round(counter.remaining, 3))
)
elif counter.remaining > 0:
print(
term.bold
+ term.red
+ term.move_x(0)
+ term.move_up
+ term.clear_eol
+ str(round(counter.remaining, 3))
)
else:
print(
term.bold
+ term.magenta
+ term.move_x(0)
+ term.move_up
+ term.clear_eol
+ "TIME'S UP!"
)
def kb_input():
if counter.remaining <= 0:
return
with term.cbreak():
key = term.inkey(timeout=0.01).lower()
if key:
if key == "q":
print(
term.bold
+ term.magenta
+ term.move_x(0)
+ term.move_up
+ term.clear_eol
+ "Quitting..."
)
sys.exit()
elif key == "r":
counter.reset(duration=float(duration))
counter.start()
elif key == " ":
counter.pause() if counter.running else counter.resume()
async def main():
global counter
global term
global duration
duration = input("Enter countdown timer duration: ")
counter = CountTimer(duration=float(duration))
counter.start()
term = Terminal()
def _run_executor_count():
count()
def _run_executor_kb_input():
kb_input()
while counter.remaining > 0:
await asyncio.get_event_loop().run_in_executor(None, _run_executor_count)
await asyncio.get_event_loop().run_in_executor(None, _run_executor_kb_input)
await asyncio.get_event_loop().run_in_executor(None, _run_executor_count)
def async_main_entry():
asyncio.get_event_loop().run_until_complete(main())
if __name__ == "__main__":
async_main_entry()
Related
I have a program that prints time elapsed when a user first presses and holds down a key and prints again when the key is released. If after 5 seconds, the user presses and holds the up arrow on their keyboard for 3 seconds before releasing, the program should print "0:05 response 1 ON" and then "0:08 response 1 OFF". The problem I'm having is that holding down the key registers as multiple key presses, resulting in the time being printed multiple times per second. Any ideas how to treat a key being pressed and held as a single key press?
import time
from pynput import keyboard
from pynput.keyboard import Key, Listener
f = open("quick_data.txt", "a")
f.write(time.ctime() + "\n")
def show(key):
if key == keyboard.Key.enter:
global start
start = time.perf_counter()
if key == keyboard.Key.delete:
return False
if key == keyboard.Key.up:
elapsed = time.perf_counter()
x = time.gmtime(elapsed - start)
y = time.strftime('%M:%S', x)
f.write(str(y) + " response 1 ON" + "\n")
def on_release(key):
if key == keyboard.Key.up:
elapsed = time.perf_counter()
x = time.gmtime(elapsed - start)
y = time.strftime('%M:%S', x)
f.write(str(y) + " response 1 OFF" + "\n")
with keyboard.Listener(
on_press=show,
on_release=on_release) as listener:
listener.join()
The library does not natively provide key press holding support, but you can easily utilize a timer to emulate said functionality.
Just initiate a timer at the start to register the time when the key associated with "RESPONSE 1" was pressed. If the new key input's time differs from the last keypress's time by more than the threshold, then it's (probably) a new keypress, and hence you write to the file "response 1 ON". Then you reset the time of the last detected keypress of RESPONSE 1 to ensure the keypress algorithm is in sync. When you release the key, on_release triggers and completes the pair. Of course, you can adjust and augment the threshold to suit your specific needs.
from pynput import keyboard
from pynput.keyboard import Key, Listener
f = open("quick_data.txt", "a")
f.write(time.ctime() + "\n")
pushdown_up = time.perf_counter() # initialize variable to store the time when the key associated with RESPONSE 1 was pressed
def show(key):
threshold = 0.10 # threshold for input timing to differentiate between a key being held and a new key
if key == keyboard.Key.enter:
global start
start = time.perf_counter()
print(start)
if key == keyboard.Key.delete:
return False
if key == keyboard.Key.up:
elapsed = time.perf_counter()
global pushdown_up # time of last detected keypress
if elapsed - pushdown_up < threshold:
# if another INPUT 1 press is detected again within a short threshold, then it is probably part of the current input press
pass
else: # else, then it is a start of a new input.
x = time.gmtime(elapsed - start)
y = time.strftime('%M:%S', x)
f.write(str(y) + " response 1 ON" + "\n")
print(str(y) + " response 1 ON" + "\n")
pushdown_up = elapsed # set the last registered keypress's time\
def on_release(key):
if key == keyboard.Key.up:
elapsed = time.perf_counter()
x = time.gmtime(elapsed - start)
y = time.strftime('%M:%S', x)
f.write(str(y) + " response 1 OFF" + "\n")
print(str(y) + " response 1 OFF" + "\n")
with keyboard.Listener(
on_press=show,
on_release=on_release) as listener:
listener.join()
So I am learning python and I am trying to create a code that detects the time between pressing "space" and "a" for something in Minecraft. The problem is that this program lags my keyboard/causes delay in keyboard presses.
I've narrowed the issue down to this:
while True:
if keyboard.is_pressed ('p'):
strafe45()
If I replace this with something like this: It does not cause keyboard lag.
run = 1
while run == 1:
strafe45()
I think it is because the first is constantly checking if I am typing 'p' or not, which is the cause of the lag, but how else can I write something like that? I cannot use while run == 1: because eventually it gives me an error since I am holding down 'a' and the variable 'start' does not have an assigned value any more.
Here is the full code if needed:
import keyboard
import time
import math
def strafe45():
while True:
if keyboard.is_pressed ('space'):
print ("starting timer")
start = time.perf_counter()
time.sleep(0.05)
if keyboard.is_pressed ('a'):
end = time.perf_counter()
print ("ending timer")
tickTime = end - start
tick = 0.05
if tickTime > tick:
print ("Did not make strafe. Too slow by " + str(tickTime - tick) + "\n" +
"Time passed (r): " + str(round(tickTime/tick, 2)) + "\n" +
"Time passed (a): " + str(tickTime/tick))
break
if tickTime < tick:
print ("Did make strafe by " + str(tick - tickTime) + "\n" +
"Time passed (r): " + str(round(tickTime/tick, 2)) + "\n" +
"Time passed (a): " + str(tickTime/tick))
break
run = 1
while run == 1:
strafe45()
"""while True:
if keyboard.is_pressed ('p'):
strafe45()"""
while True:
if keyboard.is_pressed ('p'):
strafe45()
When the p key is pressed, strafe45 gets called, and some sleep calls happen as a result.
As long as the p key is not being pressed, there is a tight while loop that keeps checking for when the key gets pressed.
You should have a single while loop, outside the key-handling function, and ensure that a time.sleep call occurs every time through this loop - by putting it in the loop explicitly. If you call out to functions to handle the keys (a good idea as the code becomes more complicated), they should not have their own loop - they should just make the appropriate changes to the program state according to what was pressed.
For example:
begin = None
def begin_timing():
global begin
begin = time.perf_counter()
def end_timing():
global begin
if begin is not None: # otherwise, we weren't timing yet.
end = time.perf_counter()
print('elapsed time:', end - begin)
begin = None # so that we can begin timing again.
while True:
# There needs to be a delay each time through the loop,
# but it needs to be considerably shorter than the time interval you're
# trying to measure.
time.sleep(0.01)
if keyboard.is_pressed('b'): # begin
begin_timing()
elif keyboard.is_pressed('e'): # end
end_timing()
Rather than constantly checking each loop, add a hook and check only when a key is pressed. keyboard.on_press(callback) adds a listener to every keyboard and key that envokes the given callback. This should alleviate your latency issues. Check out the Keyboard API Page for full documentation
def check_key(x): #x should be an keyboard.KeyboardEvent
print x.name, x.scan_code, x.time
if x.name == "":
something
elif x.name == "":
something else
keyboard.on_press(check_key)
I'm making a Artificial Inteligence battery monitor look's like on iOS13 and i need to log the battery percentage/hour/plugged only when the user connect or disconnect the charger plug.
i tried to do something like:
if str(plugged) == "True":
log_file.write(current_info + "\r\n")
elif str(plugged) == "False"
log_file.write(current_info + "\r\n")
but the script don't stop to loop on "True"
Here is the main function of my code
log_file = open("activity_log.txt", "w")
while True:
battery = psutil.sensors_battery()
# Check if charger is plugged in or not
plugged = battery.power_plugged
# Check for current battery percentage
percent = str(battery.percent)
# Check for the current system time
sys_time = datetime.datetime.now()
current_info = percent + " " + str(sys_time) + " " + str(plugged)
if str(plugged) == "True":
log_file.write(current_info + "\r\n")
log_file.close()
the project on github if you want to test or implement it: https://github.com/peterspbr/battery-ai
If I have understood you correctly you want to exit the loop when variable plugged is True? Something to take into account is that Python is a string typing language, that means, that it is not the same "True" and True.
log_file = open("activity_log.txt", "w")
plugged = False
while not plugged:
battery = psutil.sensors_battery()
# Check if charger is plugged in or not
plugged = battery.power_plugged
# Check for current battery percentage
percent = str(battery.percent)
# Check for the current system time
sys_time = datetime.datetime.now()
current_info = percent + " " + str(sys_time) + " " + str(plugged)
if str(plugged) == "True":
log_file.write(current_info + "\r\n")
log_file.close()
PD: I am assuming variable batery.power_plug is a bool type.
I may have figured out what you're trying to do: you want to log information when the battery plug changes status. You're having trouble because you've done nothing to track whether or not the battery has been plugged in. Try this:
was_plugged = battery.power_plugged
while True:
...
if battery.power_plugged != was_plugged:
log_file.write(current_info + "\r\n")
was_plugged = battery.power_plugged
Please work through more tutorials on basic Python types. It's hard to follow an indirect way of checking a value: you converted a Boolean to text, and then checked against the resulting string:
if str(plugged) == "True":
All you need is the direct Boolean test:
if plugged:
Alright, so, I made a relatively small function for my bot to post messages from an API until the process at that API is complete and changes from one type of JSON code to another (that is, showing a different thing)
Gives me the following: RuntimeWarning: coroutine 'discord_status_request' was never awaited
And the code is as follows:
#client.command(name='addip')
async def discord_add_ip(ip):
monitors.add(ip)
await client.say('Added IP: {}'.format(ip))
print(monitors)
if len(monitors) < 2:
discord_status_request()
print('Initiating monitoring process.')
##asyncio.coroutine
async def discord_status_request():
global old_response
global enablemonitoring
for i in monitors:
if enablemonitoring == True:
while True:
loop_break = False
response = requests.get(url_input.format(i)).json()
status_text = str("-" + "\n" + "Start year: " + str(response['start_year'])) + \
str('\n' + 'Start time: ' + str(
response['start_time'])) + \
str('\n' + 'Percentage: ' + str(response['percent'])) + \
str('\n' + 'Current year: ' + str(
response['current_year'])) + \
str('\n' + 'Scheme №: ' + str(
response['scheme_number'])) + \
str('\n' + 'Stop year: ' + str(response['stop_year']))
new_response = response.get('percent')
if new_response == old_response:
print('Comparison: nothing new.')
time.sleep(10)
else:
old_response = new_response
print('Comparison success!')
try:
client.say(status_text)
except KeyError:
client.say("Finished calculating. :white_check_mark:")
if len(monitors) == 0:
loop_break = True
monitors.remove(i)
print('Taking a break.')
time.sleep(10)
if loop_break:
break
I've looked the error up and found the following: https://xinhuang.github.io/posts/2017-07-31-common-mistakes-using-python3-asyncio.html
So I added this:
task = loop.create_task(discord_status_request())
loop.run_until_complete(task) #(I imported AbstractEventLoop as loop)
But, you guessed it, create_task requires coro and run_until_complete requires future. I need coro and future. So what the hell are those? Can't understand.
This is going to change depending on what version of python you are running.
but if your going to use the #asyncio.coroutine
then you wont need to use the async keyword just do
#asyncio.coroutine
def discord_status_request():
yield from function()
you would also use the yield from keywords so you may want to look up yield from with async.coroutine
other wise you should just do
async def discord_status_request():
await function()
which is the way as of python 3.5
async def syntactically defines a function as being a coroutine, although it cannot contain any form of yield expression; only return and await are allowed for returning a value from the coroutine.
I think this is what your asking
This is a continuation of my previous question, with more details. (Formatting should be better, as I am on a computer now!)
So I am trying to create a game in Python, where if a number reaches a certain amount you lose. You try to keep the number under control to keep it from reaching the number it shouldn't. Now, I had an error in my previous question that said:
AttributeError: module 'core temp' has no attribute 'ct'
However, I've modified my code a bit and no longer get any errors. However, when I run the code, a function in a module I made won't run.
To make sure that anyone trying to figure out the solution has all the resources they need, I'll provide all my code.
This is the code in the file main.py:
from termcolor import colored
from time import sleep as wait
import clear
from coretemp import ct, corestart
print(colored("Begin program", "blue"))
wait(1)
clear.clear()
def start():
while True:
while True:
cmd = str(input("Core> "))
if cmd == "help":
print(
"List of commands:\nhelp - Show a list of commands\ntemp - Show the current temperature of the core in °C\ninfo - Show core info\ncool - Show coolant control"
)
break
elif cmd == "temp":
if ct < 2000:
print(
colored("Core temperature: " + str(ct) + "°C",
"green"))
elif ct < 4000:
print(
colored("Core temperature: " + str(ct) + "°C",
"yellow"))
elif ct >= 3000:
print(
colored("Core temperature: " + str(ct) + "°C", "red"))
print("Welcome to SprinkleLaboratories Main Core System")
wait(1)
print(
"\nYou have been hired as the new core operator.\nYour job is to maintain the core, and to keep the temperature at a stable number. Or... you could see what happens if you don't..."
)
wait(3)
print("\nTo view available commands, use the \"help\" command!")
cont = input("Press enter to continue> ")
clear.clear()
start()
corestart(10)
This is the code in the file clear.py:
print("clear.py working")
def clear():
print("\n"*100)
This is the code in the file coolant.py:
from time import sleep as wait
print("coolant.py working")
coolam = 100
coolactive = False
def coolact():
print("Activating coolant...")
wait(2)
coolactive = True
print("Coolant activated. "+coolam+" coolant remaining.")
This is the code in the file coretemp.py:
from coolant import coolactive
from time import sleep as wait
print("coretemp.py working")
ct = 0
def corestart(st):
global ct
ct = st
while True:
if coolactive == False:
ct = ct + 1
print(ct)
wait(.3)
else:
ct = ct - 1
print(ct)
wait(1)
Note:
Some of the code in the files are incomplete, so some things may seem like they do nothing at the moment
If you want to see the code itself, here is a link to a repl.it:
Core
Note #2:
Sorry if things aren't formatted correctly, if I did something wrong in the question, etc. I'm fairly new to asking questions on Stackoverflow!
You can't normally have two things running at once, so when you are in the while True of start() you never ever get to the next bit of your code because while True is true forver.
So, threading to the rescue! Threads allow you to have one thing going on in one place and another thing going on in another. If we put the code to update and print the temperature in its own thread, we can set that running and then go ahead and start doing the infinite loop in start() while our thread keeps running in the background.
One other note, you should be importing coretemp itself, rather than importing variables from coretemp, otherwise you will be using a copy of the variable ct in main.py when you actually want to be using the real value of ct from coretemp.
Anyhow, here's a minimal update to your code that shows the use of threads.
main.py:
from termcolor import colored
from time import sleep as wait
import clear
import coretemp
import threading
print(colored("Begin program", "blue"))
wait(1)
clear.clear()
def start():
while True:
while True:
cmd = str(input("Core> "))
if cmd == "help":
print(
"List of commands:\nhelp - Show a list of commands\ntemp - Show the current temperature of the core in °C\ninfo - Show core info\ncool - Show coolant control"
)
break
elif cmd == "temp":
if coretemp.ct < 2000:
print(
colored("Core temperature: " + str(coretemp.ct) + "°C",
"green"))
elif coretemp.ct < 4000:
print(
colored("Core temperature: " + str(coretemp.ct) + "°C",
"yellow"))
elif coretemp.ct >= 3000:
print(
colored("Core temperature: " + str(coretemp.ct) + "°C", "red"))
print("Welcome to SprinkleLaboratories Main Core System")
wait(1)
print(
"\nYou have been hired as the new core operator.\nYour job is to maintain the core, and to keep the temperature at a stable number. Or... you could see what happens if you don't..."
)
wait(3)
print("\nTo view available commands, use the \"help\" command!")
cont = input("Press enter to continue> ")
clear.clear()
coretemp.corestart(10)
t1 = threading.Thread(target=coretemp.coreactive)
t1.start()
start()
coretemp.py:
from coolant import coolactive
from time import sleep as wait
print("coretemp.py working")
ct = 0
def corestart(st):
global ct
ct = st
def coreactive():
global ct
while True:
if coolactive == False:
ct = ct + 1
print(ct)
wait(.3)
else:
ct = ct - 1
print(ct)
wait(1)
You can see we still have some work to do to make the output look nice and so on, but hopefully you get the general idea.