pynput keyboard Listener autorepeat - python

I'm trying to use pynput to get realtime keyboard input, but the on_press function gets called on autorepeat.
example code:
#!/usr/bin/env python3
import sys
import numpy as np
import sounddevice as sd
from pynput import keyboard
frequency = []
def on_press(key):
global frequency
try:
print(key.vk)
if key.char == "q":
frequency.append(440)
elif key.char == "w":
frequency.append(880)
except AttributeError:
pass
def on_release(key):
global frequency
if key == keyboard.Key.esc:
# Stop listener
print("Press Enter")
return False
elif key.char == "q":
frequency.remove(440)
elif key.char == "w":
frequency.remove(880)
listener = keyboard.Listener(
on_press=on_press,
on_release=on_release,
suppress = True)
listener.start()
start_idx = 0
def callback(outdata, frames, time, status):
if status:
print(status, file=sys.stderr)
print(frames)
global start_idx
t = (start_idx + np.arange(frames)) / 48000
t = t.reshape(-1, 1)
outdata[:] = 0 * t
if len(frequency) > 0:
print("Playing")
for freq in frequency:
outdata[:] = outdata[:] + 0.2 * np.sin(2 * np.pi * freq * t)
start_idx += frames
try:
with sd.OutputStream(channels=1, callback=callback,
samplerate=48000):
input()
listener.stop()
except Exception as e:
print("Exception")
listener.stop()
exit()
If you run the code and press and hold the Q key, the keyboard autorepeat kicks in and ruins the whole listener. Is there a python input module which handles raw keyboard input properly?
The second thing is that the code tends to crash my Xorg quite regularly. I just run the script a few times and the Xorg goes down. And I can't figure out why. Linux 5.5.2-zen1-1-zen x86_64 GNU/Linux, X.Org 1.20.7.
The third thing is that the sound synthesis seems to lag quite a bit. The number of frames to the callback function seems to hang around 400, which at the rate of 48000 samples per second is less then 10 milliseconds, but the actual audio feedback feels like on hundreds of milliseconds delay.

pygame has a good keylistener and is easy to build a GUI window that displays output. It also works well in Linux:
import pygame
def main_loop():
#code
loopExit = False
while not loopExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
loopExit = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
#code
if event.type == pygame.KEYUP:
if event.key == pygame.K_q:
#code
https://www.pygame.org/docs/ref/key.html

Related

How to Get Input from Joystick in Python?

I'm trying to get input from a joystick I have (specifically the Logitech Extreme 3D Pro) with a Python program. Unfortunately, I do not know how to do this well.
I currently have a working prototype using PyGame, but I do not want to use PyGame because I already want another Tkinter window open at the same time.
I've tried the inputs library, but I keep getting an inputs.UnpluggedError every time I plug the joystick in.
Are there any other methods of getting joystick input, other than PyGame?
I am using an MacBook Air running Big Sur.
Working code:
import os
import pprint
import pygame
import threading
class PS4Controller(object):
"""Class representing the PS4 controller. Pretty straightforward functionality."""
controller = None
axis_data = None
button_data = None
hat_data = None
def init(self):
"""Initialize the joystick components"""
pygame.init()
pygame.joystick.init()
self.controller = pygame.joystick.Joystick(0)
self.controller.init()
def listen(self):
"""Listen for events to happen"""
if not self.axis_data:
self.axis_data = {}
if not self.button_data:
self.button_data = {}
for i in range(self.controller.get_numbuttons()):
self.button_data[i] = False
if not self.hat_data:
self.hat_data = {}
for i in range(self.controller.get_numhats()):
self.hat_data[i] = (0, 0)
while True:
for event in pygame.event.get():
if event.type == pygame.JOYAXISMOTION:
self.axis_data[event.axis] = round(event.value,2)
elif event.type == pygame.JOYBUTTONDOWN:
self.button_data[event.button] = True
elif event.type == pygame.JOYBUTTONUP:
self.button_data[event.button] = False
elif event.type == pygame.JOYHATMOTION:
self.hat_data[event.hat] = event.value
# Insert your code on what you would like to happen for each event here!
# In the current setup, I have the state simply printing out to the screen.
os.system('clear')
pprint.pprint(self.button_data)
pprint.pprint(self.axis_data)
pprint.pprint(self.hat_data)
def controller_main():
ps4 = PS4Controller()
ps4.init()
ps4.listen()
controller_main()
Try this py-joystick module:
https://pypi.org/project/pyjoystick/
Hope it helps!😊
from pyjoystick.sdl2 import Key, Joystick, run_event_loop
def print_add(joy):
print('Added', joy)
def print_remove(joy):
print('Removed', joy)
def key_received(key):
print('received', key)
if key.value == Key.HAT_UP:
#do something
elif key.value == Key.HAT_DOWN:
#do something
if key.value == Key.HAT_LEFT:
#do something
elif key.value == Key.HAT_UPLEFT:
#do something
elif key.value == Key.HAT_DOWNLEFT:
#do something
elif key.value == Key.HAT_RIGHT:
#do something
elif key.value == Key.HAT_UPRIGHT:
#do something
elif key.value == Key.HAT_DOWNRIGHT:
#do something
run_event_loop(print_add, print_remove, key_received)

Pygame: Adding an Escape key to exit whole game with stopwatch function

I'm making a game via Python on a Raspberry Pi. I'm using the GPIOs to light up an LED and detect a button switch.
I wanted to incorporate an ESC on the keyboard so we can exit at any time.
But whenever I add in the ESC key code into the main while loop. It doesn't work. The LED and Buttons work, but when I press on the ESC key, it doesn't do anything.
The loop runs to refresh/run a stopwatch and listen to an LED button via the GPIO.
I wanted some advice on how things like ESC key are handled in games. Especially with fast paced games where the loop and cycles are very fast.
Any tips or suggestions would be greatly appreciated. Thanks in advance.
Please see the code below:
# Importing all libraries
import RPi.GPIO as GPIO
import sys, time, atexit, pygame
# Setup GPIO and Pygame
GPIO.setmode(GPIO.BCM)
pygame.init()
# Define Tuples and Variables
leds = (16,17,22,9,5)
switches = (19,4,27,10,11)
button_pressed = False
taskcomplete = False
# Pygame visual variables
screen = pygame.display.set_mode( (1024,240) )
counterfont = pygame.font.Font('DSEG14Modern-Regular.ttf', 70)
# Set Pygame refresh rate variable = clock
clock = pygame.time.Clock()
# Clock variables
sec_val = 0
sec = 0
mins = 0
hours = 0
# Status variables
paused = False
running = True
# Start the clock
start_time = pygame.time.get_ticks()
# Defining Functions
# Function that renders segment display on screen
def time_convert(sec):
sec = sec % 60
sec_val = ("Timer: {0}".format(round((sec), 2)))
counting_text = counterfont.render(str(sec_val), 3, (134,145,255))
counting_rect = counting_text.get_rect(left = screen.get_rect().left)
screen.fill( (0,0,0) )
screen.blit(counting_text, (300,40))
pygame.display.update()
# Stopwatch function to compute for a SS:MS based stopwatch
def stop_Watch():
end_time = time.time()
time_lapsed = end_time - start_time
sec_val = time_convert(time_lapsed)
# Press Button 1 to start the game
def but_3():
while GPIO.input(switches[2]) == GPIO.LOW:
GPIO.output(leds[2],True)
time.sleep(0.01)
stop_Watch()
GPIO.output(leds[2],False)
print(" Button 3 is pressed! Exit")
start_time = time.time()
def buttonPress(channel):
# This function gets called every time a button is pressed, if the button pressed is the same as the button
# that is illuminated, then we set the "correct_button" variable to True,
# otherwise we set the "incorrect_button" variable to True.
# We need to set some variables to global so that this function can change their value.
button_pressed = True
def exit():
# This function gets called when we exit our script, using Ctrl+C
print("GPIO Clean Up!")
GPIO.cleanup()
pygame.quit()
# This tells our script to use the "exit()" without this, our "exit()" function would never be called.
atexit.register(exit)
#Loop through the leds to set them up
for led in leds:
# Set the led to be an ouput
GPIO.setup(led, GPIO.OUT)
# Turn the led off
GPIO.output(led,False)
# Loop through the switches to set them up
for switch in switches:
# Set the switch to be an input
GPIO.setup(switch, GPIO.IN)
# Add rising edge detection
GPIO.add_event_detect(switch, GPIO.RISING, bouncetime=300)
# Add the function "buttonPress" to be called when switch is pressed.
GPIO.add_event_callback(switch, buttonPress)
# Main sequence code
# Setup Pygame refresh rate to 120 fps
clock.tick(120)
# Start timer
start_time = time.time()
# Main loop
while running:
# Press Button 1 to start the game
while GPIO.input(switches[0]) == GPIO.LOW:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
print("escape pressed")
running = False
GPIO.output(leds[0],True)
time.sleep(0.01)
stop_Watch()
GPIO.output(leds[0],False)
print(" Button 1 is pressed! Exit")
running = False
exit()
It's because it's in another while loop I'm guessing, so it's not in the running while loop anymore. You can add pygame.quit() to make it quit that way though:
# Main loop
while running:
# Press Button 1 to start the game
while GPIO.input(switches[0]) == GPIO.LOW:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
print("escape pressed")
pygame.quit()
running = False
GPIO.output(leds[0],True)
time.sleep(0.01)
stop_Watch()
GPIO.output(leds[0],False)
print(" Button 1 is pressed! Exit")
running = False
exit()
Or, since you have a function named exit() that does the same thing, you can add exit() to those places instead.

How to identify which button is being pressed on PS4 controller using pygame

I am using a Raspberry Pi 3 to control a robotic vehicle. I have successfully linked my PS4 controller to the RPi using ds4drv. I have the following code working and outputting "Button Pressed"/"Button Released" when a button is pressed/released on the PS4 controller using pygame. I am wondering how to identify which button is exactly being pressed.
ps4_controller.py
import pygame
pygame.init()
j = pygame.joystick.Joystick(0)
j.init()
try:
while True:
events = pygame.event.get()
for event in events:
if event.type == pygame.JOYBUTTONDOWN:
print("Button Pressed")
elif event.type == pygame.JOYBUTTONUP:
print("Button Released")
except KeyboardInterrupt:
print("EXITING NOW")
j.quit()
Figured out a hack.
The PS4 buttons are numbered as the following:
0 = SQUARE
1 = X
2 = CIRCLE
3 = TRIANGLE
4 = L1
5 = R1
6 = L2
7 = R2
8 = SHARE
9 = OPTIONS
10 = LEFT ANALOG PRESS
11 = RIGHT ANALOG PRESS
12 = PS4 ON BUTTON
13 = TOUCHPAD PRESS
To figure out which button is being pressed I used j.get_button(int), passing in the matching button integer.
Example:
import pygame
pygame.init()
j = pygame.joystick.Joystick(0)
j.init()
try:
while True:
events = pygame.event.get()
for event in events:
if event.type == pygame.JOYBUTTONDOWN:
print("Button Pressed")
if j.get_button(6):
# Control Left Motor using L2
elif j.get_button(7):
# Control Right Motor using R2
elif event.type == pygame.JOYBUTTONUP:
print("Button Released")
except KeyboardInterrupt:
print("EXITING NOW")
j.quit()
You are really close! With a few tweaks, you code becomes this instead:
import pygame
pygame.init()
j = pygame.joystick.Joystick(0)
j.init()
try:
while True:
events = pygame.event.get()
for event in events:
if event.type == pygame.JOYAXISMOTION:
print(event.dict, event.joy, event.axis, event.value)
elif event.type == pygame.JOYBALLMOTION:
print(event.dict, event.joy, event.ball, event.rel)
elif event.type == pygame.JOYBUTTONDOWN:
print(event.dict, event.joy, event.button, 'pressed')
elif event.type == pygame.JOYBUTTONUP:
print(event.dict, event.joy, event.button, 'released')
elif event.type == pygame.JOYHATMOTION:
print(event.dict, event.joy, event.hat, event.value)
except KeyboardInterrupt:
print("EXITING NOW")
j.quit()
Some resources that I found helpful in writing the up included pygame's event documentation, the use of python's dir function to see what properties a python object has, and the documentation for pygame's parent C library, SDL if you wanted a deeper explanation of what the property actually means. I included both the dictionary access version (using event.dict) as well as the property-access version (using just event.whatever_the_property_name_is). Note that event.button only gives you a number; it is up to you to manually create a mapping of what each button number means on your controller. Hope this clears it up!
A bit late, but if there are people looking for a solution on this still, I have created a module: pyPS4Controller that has all the button events on the controller already mapped and they can be overwritten like so:
from pyPS4Controller.controller import Controller
class MyController(Controller):
def __init__(self, **kwargs):
Controller.__init__(self, **kwargs)
def on_x_press(self):
print("Hello world")
def on_x_release(self):
print("Goodbye world")
controller = MyController(interface="/dev/input/js0", connecting_using_ds4drv=False)
# you can start listening before controller is paired, as long as you pair it within the timeout window
controller.listen(timeout=60)
This is just an example for 1 event. There are more events that can be overwritten such as:
on_x_press
on_x_release
on_triangle_press
on_triangle_release
on_circle_press
on_circle_release
on_square_press
on_square_release
on_L1_press
on_L1_release
on_L2_press
on_L2_release
on_R1_press
on_R1_release
on_R2_press
on_R2_release
on_up_arrow_press
on_up_down_arrow_release
on_down_arrow_press
on_left_arrow_press
on_left_right_arrow_release
on_right_arrow_press
on_L3_up
on_L3_down
on_L3_left
on_L3_right
on_L3_at_rest # L3 joystick is at rest after the joystick was moved and let go off
on_L3_press # L3 joystick is clicked. This event is only detected when connecting without ds4drv
on_L3_release # L3 joystick is released after the click. This event is only detected when connecting without ds4drv
on_R3_up
on_R3_down
on_R3_left
on_R3_right
on_R3_at_rest # R3 joystick is at rest after the joystick was moved and let go off
on_R3_press # R3 joystick is clicked. This event is only detected when connecting without ds4drv
on_R3_release # R3 joystick is released after the click. This event is only detected when connecting without ds4drv
on_options_press
on_options_release
on_share_press # this event is only detected when connecting without ds4drv
on_share_release # this event is only detected when connecting without ds4drv
on_playstation_button_press # this event is only detected when connecting without ds4drv
on_playstation_button_release # this event is only detected when connecting without ds4drv
Full documentation is available # https://github.com/ArturSpirin/pyPS4Controller
if event.type == pygame.JOYBUTTONDOWN:
if j.get_button(0):
PXV = -0.1
if j.get_button(2):
PXV = 0.1
if event.type == pygame.JOYBUTTONUP:
if j.get_button(0) or j.get_button(2):
PXV = 0
joy button down works but PXV(player x velocity)
does not get reset back to zero when i release my controller

Omxplayer fill mode not working

Working on a video project for raspberry pi. Trying to get video to fill entire screen. When I call omxplayer videofile.mp4 --aspect-mode fill it plays fine. However when I call it in my program the argument for the aspect ratio is not working.
import pygame
import sys
from time import sleep
from omxplayer import OMXPlayer
pygame.joystick.init()
_joystick_right = pygame.joystick.Joystick(0)
_joystick_right.init()
_joystick_left = pygame.joystick.Joystick(1)
_joystick_left.init()
pygame.init()
done = False
button = ''
controller = ''
player = ''
path = 'hankvids/'
movies = [
'vid1.mp4',
'vid2.mp4',
]
quit_video = False
while done==False:
for event in pygame.event.get():
if event.type == pygame.JOYBUTTONDOWN:
button = event.button
controller = event.joy
if quit_video == True:
if button == 0 and controller == 0:
player.quit()
quit()
else:
if player.is_playing():
player.load(path + movies[controller], pause=False)
else:
quit_video = True
player = OMXPlayer(path + movies[controller], args=["-b --aspect-mode fill"], pause=False)
You need to split the command into words: args=["-b", "--aspect-mode", "fill"],. Alternatively you can pass your CLI string to shlex to split the args string for you.

Python while key pressed function [duplicate]

I am controlling a remote toy car using python code. As of now, the code is as below:
def getkey():
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
new = termios.tcgetattr(fd)
new[3] = new[3] & ~TERMIOS.ICANON & ~TERMIOS.ECHO
new[6][TERMIOS.VMIN] = 1
new[6][TERMIOS.VTIME] = 0
termios.tcsetattr(fd, TERMIOS.TCSANOW, new)
c = None
try:
c = os.read(fd, 1)
finally:
termios.tcsetattr(fd, TERMIOS.TCSAFLUSH, old)
return c
def car():
while True:
key = getkey()
if key == 's': #Down arrow
print "Down"
Backward()
elif key == 'w': #Up arrow
print "Up"
forward()
elif key == 'a':
print "left"
Left()
elif key == 'd':
print "Right"
Right()
elif key == 'q': #Quit
print "That's It"
break
def forward():
GPIO.output(11,True) #Move forward
When I press 'w' forward() method is called and the car moves forward but wont stop until I quit the program or call GPIO.output(11, False) from some other method.
Is there any key Listener which detects the key release of any particular key?
For example, if 'w' pressed called this method and if released call some other method
Sudo code:
if w_isPressed()
forward()
else if w_isReleased()
stop()
I've seen Pygame game development library being successfully used in similar scenarios before, handling realtime systems and machinery in production, not just toy examples. I think it's a suitable candidate here too. Check out pygame.key module for what is possible to do with the keyboard input.
In short, if you are not familiar with game development, you basically continuously poll for events such as input state changes inside an 'infinite' game loop and react accordingly. Usually update the parameters of the system using deltas per time elapsed. There's plenty of tutorials on that and Pygame available around and Pygame docs are pretty solid.
A simple example of how to go about it:
import pygame
pygame.init()
# to spam the pygame.KEYDOWN event every 100ms while key being pressed
pygame.key.set_repeat(100, 100)
while 1:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_w:
print 'go forward'
if event.key == pygame.K_s:
print 'go backward'
if event.type == pygame.KEYUP:
print 'stop'
You'll need to play with pygame.KEYDOWN, pygame.KEYUP and pygame.key.set_repeat depending on how your car movement is implemented.
Faced a similar problem (I am no Python expert) but this worked for me
import pynput
from pynput import keyboard
def on_press(key):
try:
print('Key {0} pressed'.format(key.char))
#Add your code to drive motor
except AttributeError:
print('Key {0} pressed'.format(key))
#Add Code
def on_release(key):
print('{0} released'.format(key))
#Add your code to stop motor
if key == keyboard.Key.esc:
# Stop listener
# Stop the Robot Code
return False
# Collect events until released
with keyboard.Listener(
on_press=on_press,
on_release=on_release) as listener:
listener.join()

Categories