Skip to content

Instantly share code, notes, and snippets.

@raoulduke
Created March 5, 2026 06:52
Show Gist options
  • Select an option

  • Save raoulduke/231ba1008ed401c82bb7e13a76dd98ef to your computer and use it in GitHub Desktop.

Select an option

Save raoulduke/231ba1008ed401c82bb7e13a76dd98ef to your computer and use it in GitHub Desktop.
Raspberry Pi - Read all USB input devices with keyboard capabilities
import sys
import termios
import tty
from evdev import InputDevice, list_devices, categorize, ecodes
from select import select
# Key constants
KEY_UP = "UP"
KEY_DOWN = "DOWN"
KEY_LEFT = "LEFT"
KEY_RIGHT = "RIGHT"
KEY_ENTER = "ENTER"
KEY_QUIT = "QUIT"
KEY_BACK = "BACK"
KEY_BACKSPACE = "BACKSPACE"
class InputReader:
def __init__(self):
# Get all USB input devices with keyboard capabilities
self.devices = {}
for path in list_devices():
device = InputDevice(path)
capabilities = device.capabilities()
# Only monitor devices with keyboard keys
if ecodes.EV_KEY in capabilities:
self.devices[device.fd] = device
self.stdin_fd = sys.stdin.fileno()
self.old_settings = termios.tcgetattr(self.stdin_fd)
tty.setraw(self.stdin_fd)
if self.devices:
print("Monitoring USB input devices:\r")
for device in self.devices.values():
print(f" - {device.name}\r")
print("Also monitoring SSH keyboard input\r")
def __del__(self):
try:
termios.tcsetattr(self.stdin_fd, termios.TCSADRAIN, self.old_settings)
except:
pass
def read_stdin_key(self):
ch = sys.stdin.read(1)
if ch == "\x1b": # ESC
ch2 = sys.stdin.read(1)
if ch2 == "[":
ch3 = sys.stdin.read(1)
key_map = {
"A": KEY_UP,
"B": KEY_DOWN,
"C": KEY_RIGHT,
"D": KEY_LEFT,
}
return key_map.get(ch3)
return KEY_QUIT
if ch in ("\r", "\n"):
return KEY_ENTER
if ch == "\x7f":
return KEY_BACKSPACE
if ch.lower() == "q":
return KEY_QUIT
# Return any printable character as-is for text input
if ch.isprintable():
return ch
return None
def read_usb_key(self, fd):
device = self.devices[fd]
for event in device.read():
if event.type == ecodes.EV_KEY and event.value == 1: # Key down only
key_event = categorize(event)
keycode = key_event.keycode
# Handle key codes (can be string or list)
if isinstance(keycode, list) or isinstance(keycode, tuple):
keycode = keycode[0]
key_map = {
'KEY_UP': KEY_UP,
'KEY_DOWN': KEY_DOWN,
'KEY_LEFT': KEY_LEFT,
'KEY_RIGHT': KEY_RIGHT,
'KEY_ENTER': KEY_ENTER,
'KEY_KPENTER': KEY_ENTER,
'KEY_Q': KEY_QUIT,
'KEY_ESC': KEY_QUIT,
'BTN_RIGHT': KEY_ENTER,
'KEY_BACKSPACE': KEY_BACKSPACE,
'KEY_DELETE': KEY_BACKSPACE,
}
result = key_map.get(keycode)
if result:
return result
if isinstance(keycode, str) and keycode.startswith('KEY_'):
key_char = keycode.replace('KEY_', '')
# Single letter keys (A-Z)
if len(key_char) == 1 and key_char.isalpha():
return key_char.lower()
# Number keys on main keyboard
if len(key_char) == 1 and key_char.isdigit():
return key_char
# Number pad keys
if key_char.startswith('KP') and len(key_char) == 3:
return key_char[2] # KP0 -> 0, KP1 -> 1, etc.
# Handle special characters
char_map = {
'SPACE': ' ',
'MINUS': '-',
'EQUAL': '=',
'LEFTBRACE': '[',
'RIGHTBRACE': ']',
'SEMICOLON': ';',
'APOSTROPHE': "'",
'GRAVE': '`',
'BACKSLASH': '\\',
'COMMA': ',',
'DOT': '.',
'SLASH': '/',
'KPASTERISK': '*',
'KPMINUS': '-',
'KPPLUS': '+',
'KPDOT': '.',
'KPSLASH': '/',
}
if key_char in char_map:
return char_map[key_char]
return None
def read_key(self):
while True:
read_fds = list(self.devices.keys()) + [self.stdin_fd]
r, w, x = select(read_fds, [], [])
for fd in r:
if fd == self.stdin_fd:
# SSH keyboard input
result = self.read_stdin_key()
if result:
return result
else:
# USB keyboard input
result = self.read_usb_key(fd)
if result:
return result
_input_reader = None
def read_key():
global _input_reader
if _input_reader is None:
_input_reader = InputReader()
return _input_reader.read_key()
# Set of command strings that are not printable characters
_COMMANDS = {KEY_UP, KEY_DOWN, KEY_RIGHT, KEY_LEFT, KEY_ENTER, KEY_QUIT, KEY_BACKSPACE}
def read_text(lcd, prompt):
text = ""
while True:
if len(text) <= 15:
display_text = (text + "_")[:16]
else:
display_text = text[-16:]
lcd.write(prompt[:16], display_text)
key = read_key()
if key == KEY_QUIT:
return KEY_QUIT
if key == KEY_LEFT:
return KEY_BACK
if key == KEY_ENTER:
if text.strip():
return text.strip()
continue
if key == KEY_BACKSPACE:
text = text[:-1]
continue
if key and key not in _COMMANDS and len(key) == 1:
text += key
def format_name(name: str) -> str:
name = name.replace("_", " ").replace("-", " ").strip()
if len(name) == 2 and name != "on":
return name.upper()
return name.title()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment