I'm trying to make a note application in Python using curses.
To the bottom left, should be a clock that updates every second.
The issue I now have is that it either has to sleep 1 second, or wait for input.
Is it possible to wait for input for 1 second and continue if no input it registered?
The reason I want to do this, is to prevent delay when moving around in the application.
I was thinking something like multi-threading would do the job, but got some issues there too.
This is the code I have so far:
#!/usr/bin/env python3
import curses
import os
import time
import datetime
import threading
def updateclock(stdscr):
while True:
height, width = stdscr.getmaxyx()
statusbarstr = datetime.datetime.now().strftime(' %A')[:4] + datetime.datetime.now().strftime(' %Y-%m-%d | %H:%M:%S')
stdscr.addstr(height-1, 0, statusbarstr)
time.sleep(1)
def draw_menu(stdscr):
k = 0
stdscr.clear()
stdscr.refresh()
threading.Thread(target=updateclock, args=stdscr).start()
cursor_y = 0
cursor_x = 0
while (k != ord('q')):
#while True:
stdscr.clear()
height, width = stdscr.getmaxyx()
stdscr.addstr(height//2, width//2, "Some text in the middle")
if k == curses.KEY_DOWN:
cursor_y = cursor_y + 1
elif k == curses.KEY_UP:
cursor_y = cursor_y - 1
elif k == curses.KEY_RIGHT:
cursor_x = cursor_x + 1
elif k == curses.KEY_LEFT:
cursor_x = cursor_x - 1
stdscr.refresh()
#time.sleep(1)
# Wait for next input
k = stdscr.getch()
curses.wrapper(draw_menu)
The code looks pretty messy, and it's the first time I've mainly focused on the curses function.
Is it possible to only wait for input k = stdscr.getch() for 1 second?
By default getch will block until you have a character input ready. If nodelay mode is True, then you will either get the character value (0-255) of the character that is ready, or you will get a -1 indicating that no character value is ready.
stdscr.nodelay(True) #Set nodelay to be True, it won't block anymore
k = stdscr.getch() #Either the next character of input, or -1
Related
I'm trying to figure out how I can solve this issue. I have a Raspberry Pi that is set up with a breadboard that consists of:
1 RGB light
2 buttons (left and right)
1 OLED screen
Each component works and I can run each one. What I'm trying to do is write a script that will allow me to select the "mode" with the left button (everything off vs lights vs screen on).
When a mode is selected, the right button then allows me to select between options within that mode. Below is the code as I have it:
def off():
lights = [red,green,blue]
for light in lights:
light.off()
def lightSelector():
off()
number = 0
while number < 5:
if rightButton.is_pressed:
if number == 0:
off()
red.on()
sleep(1)
number += 1
elif number == 1:
off()
green.on()
sleep(1)
number += 1
elif number == 2:
off()
blue.on()
sleep(1)
number += 1
elif number == 3:
off()
row()
sleep(1)
number+= 1
else:
number = 0
def picture():
image = Image.open('grant.jpeg')
image_r = image.resize((width,height), Image.BICUBIC)
image_bw = image_r.convert("1")
for x in range(width):
for y in range(height):
oled.pixel(x,y,bool(int(image_bw.getpixel((x,y)))))
oled.show()
def oledOff():
oled.fill(0)
oled.show()
def buttons():
x = 0
y = 0
while y is 0:
print('x = ' , x)
print('y = ' , y)
if leftButton.is_pressed:
if x == 0 :
oledOff()
off()
sleep(0.5)
x += 1
elif x == 1:
oledOff()
off()
lightSelector()
sleep(0.5)
x += 1
elif x == 2:
oledOff()
off()
picture()
sleep(0.5)
x += 1
else:
x = 0
oledOff()
off()
buttons()
The idea is that the buttons() function is the main overall function and will call the others as needed. My issue is that once I get to y == 1, or the lightSelector() function, it no longer registers that the leftButton is being pressed and won't switch to the next mode and I'm stuck in the lightSelector() function.
I know at baseline I can spell out lightSelector within the buttons function instead of calling another function but I'm trying to not be as verbose. I don't have any experience with threading or multprocessing and looked into it but couldn't see how that would help.
So I'm making a game without PyGame, and I want to add a section where you try to press the correct keyboard letters given the "number" of the letter, which would mean A - 1, B - 2, C - 3, etc. I want to make it so you can't mash every key, so I added a counter. However - the counter doesn't work. Help!
def keyboardpart(l,x,num):
for i in range(num):
keypress = False
c = 0
dn = random.randint(0,25)
var = l[dn]
print(dn+1)
flag1 = False
start = time.time()
while time.time()-start < x:
if keypress and not keyboard.is_pressed(var):
if c > 3:
break
c+=1
elif keyboard.is_pressed(var) and not keypress:
keypress = True
print(keypress,c)
if not keypress:
print("Sorry, you missed that key.")
flag1 = True
break
if flag1:
keyboardpart(l,x,num)
Check if any keys are pressed then check if the target key is pressed. Also wait until all keys are released before retry.
Try this code:
import random, keyboard, time
l = [chr(65+i) for i in range(26)] # A-Z
def keyboardpart(l,x,num):
for i in range(num):
keyboard.is_pressed(65) # process OS events
while len(keyboard._physically_pressed_keys): pass # wait until keys released
keypress = False
c = 0
dn = random.randint(0,25)
var = l[dn]
print(dn+1, l[dn])
flag1 = False
start = time.time()
while time.time()-start < x:
if len(keyboard._physically_pressed_keys): # any key down
if keyboard.is_pressed(var): # check target key
keypress = True
break
else: # wrong key
c+=1
if c >= 3: break # allow 3 tries
while len(keyboard._physically_pressed_keys): pass # wait until keys released
print(keypress, c)
print(keypress,c)
if not keypress:
print("Sorry, you missed that key.")
flag1 = True
break
if flag1:
keyboardpart(l,x,num)
keyboardpart(l,100,4)
Capturing the return key causes my loop to run twice, other keys only return once. Code below captures arrow keys and return key, with a counter to visualise how many times the code is run. Upon hitting the return key, the counter will advance by 2 instead of 1.
Using pycharm on mac os, with pycharm's environment set to emulate the terminal in the output console.
Thanks for any help.
import curses
from curses import wrapper
def main(stdscr):
# Clear screen
stdscr.clear()
stdscr.addstr("test")
stdscr.addstr(3, 3, "x")
stdscr.addstr(6, 6, "x")
stdscr.clear()
n = 0
while True:
c = stdscr.getch()
stdscr.clear()
stdscr.addstr(str(n))
stdscr.move(2, 0)
if c == curses.KEY_UP:
stdscr.addstr("up")
elif c == curses.KEY_DOWN:
stdscr.addstr("down")
elif c == curses.KEY_LEFT:
stdscr.addstr("left")
elif c == curses.KEY_RIGHT:
stdscr.addstr("right")
elif c == curses.KEY_ENTER or c == 10 or c == 13:
stdscr.addstr("enter")
n +=1
wrapper(main)
I'm trying to create a setup to blink a led and be able to control the frequency.
Right now I'm just printing 10s as placeholders for testing.
Everything runs and does what should, but getch is throwing me off.
freq = 1
while freq > 0:
time.sleep(.5/freq) #half dutycycle / Hz
print("1")
time.sleep(.5/freq) #half dutycycle / Hz
print("0")
def kbfunc():
return ord(msvcrt.getch()) if msvcrt.kbhit() else 0
#print(kbfunc())
if kbfunc() == 27: #ESC
break
if kbfunc() == 49: #one
freq = freq + 10
if kbfunc() == 48: #zero
freq = freq - 10
Now when it starts up, the freq change portion seems buggy like it's not reading all the time or I have to time the press just right. The break line has no problem whenever pressed though.
There should be only one kbfunc() call. Store the result in a variable.
E.g.: In your code if the key isn't Esc, you'll read the keyboard again.
from msvcrt import getch,kbhit
import time
def read_kb():
return ord(getch()) if kbhit() else 0
def next_state(state):
return (state + 1)%2 # 1 -> 0, 0 -> 1
freq = 1.0 # in blinks per second
state = 0
while freq > 0:
print(state)
state = next_state(state)
key = read_kb()
if key == 27: #ESC
break
if key == 49: #one
freq = freq + 1.0
if key == 48: #zero
freq = max(freq - 1.0, 1.0)
time.sleep(0.5/freq)
Using Python, I am trying to write a script which will convert all typed characters into 'a' whenever you pressed space-bar. For example, i typed "python" and then space, then "python" will convert into "aaaaaa".
import argparse
import curses
import time
# Main Function
def main():
screen=curses.initscr()
curses.cbreak()
screen.keypad(1)
curses.echo()
str_txt=''
count = 0
while True:
s=screen.getch()
if s != ord(' ') and s != ord('\x1b') and s != curses.KEY_BACKSPACE and s != curses.KEY_ENTER:
str_txt += chr(int(s))
count+=1
if s == ord(' '):
dim = screen.getyx()
h = 'a'*len(str_txt)+' '
screen.addstr(dim[0],dim[1]-count-1, h)
count=0
str_txt=''
screen.refresh()
if s == curses.KEY_ENTER or s==10 or s==13:
dim = screen.getyx()
screen.move(dim[0]+1,0)
screen.refresh()
#if s == curses.KEY_BACKSPACE:
# dim = screen.getyx()
# screen.move(dim[0],dim[1])
# screen.refresh()
if s == ord('\x1b'):
curses.endwin()
break
if __name__ == "__main__":
main()
The above code works fine for 1st line, however, in the second line whenever i press spacebar, i am getting an error on line 22 saying "_curses.error: addstr() returned ERR"
EDITED:
When I change screen.addstr(dim[0],dim1-count-1, h) to screen.addstr(dim[0],dim1-count, h), the error is eliminated but the output is not what I want. I have attached to output for your reference.
if s != ord(' ') and s != ord('\x1b') and s != curses.KEY_BACKSPACE:
str_txt += chr(int(s))
count+=1
That if statement executes for the carriage return and\or newline char too I think, so I think your count is 1 over what you expected, due to the first line.
The addstr() returned ERR exception is because the cursor is being moved off screen (out of bound) due to:
screen.addstr(dim[0],dim[1]-count-1, h)
Since your count is +1 due to the carriage return (\r) at the end of your first line. The first if should check this and not increase the count. Try to add these checks s!=curses.KEY_ENTER and s!=10 and s!=13 to the first if and see if that helps. s!=10 will check the new line char (\n) (which may not be necessary in this case, but I am OCD). s!=13 will check the carriage return character (\r).
There was more than one place to improve the given example. Here is a revised version:
import curses
import time
# Main Function
def main():
screen=curses.initscr()
curses.cbreak()
screen.keypad(1)
curses.echo()
screen.scrollok(1)
str_txt=''
count = 0
while True:
dim = screen.getyx()
s=screen.getch()
if s != ord(' ') and s != ord('\x1b') and s != curses.KEY_BACKSPACE and s != curses.KEY_ENTER and s != 10 and s != 13:
str_txt += chr(int(s))
count+=1
if s == ord(' '):
if count > 0:
h = 'a'*len(str_txt)+' '
screen.addstr(dim[0],dim[1]-count, h)
count=0
str_txt=''
if s == curses.KEY_ENTER or s==10 or s==13:
if count > 0:
h = 'a'*len(str_txt)
screen.addstr(dim[0],dim[1]-count, h)
count=0
str_txt=''
screen.move(dim[0]+1,0)
count=0
str_txt=''
#if s == curses.KEY_BACKSPACE:
# dim = screen.getyx()
# screen.move(dim[0],dim[1])
# screen.refresh()
if s == ord('\x1b'):
curses.endwin()
break
if __name__ == "__main__":
main()
For example:
the screen.refresh calls are not needed, since screen.getch does that.
no check was made to ensure that the count was nonzero
pressing enter (or return) did not behave the "same" as space.
the movement after enter/return moved two lines rather than one.
the screen would not scroll at the end (partial fix in this example)