With anything Linux, the easiest is to implement an USB HID or PS/2 keyboard in hardware, whichever the Linux-based host already uses. If you use a previously unused interface type, you may need to install the missing kernel modules (or, in the case of an optimized monolithic kernel, recompile a kernel with the necessary features enabled).
The easiest way to implement new keyboard/mouse/trackpad/joystick/gamepad-like HID devices I know of currently, is to use a
Teensy 4.0 (or, if you happen to have one, a
Teensy LC or a
Teensy 3.2, which are no longer produced due to chipageddon and resulting IC availability and price issues) and the
Teensyduino add-on to Arduino environment programming. It has a native USB interface, and you can select the type of your device from the Arduino IDE menu (
Tools >
USB type in Arduino 1.8.x), including Serial, Raw HID, Keyboard, Keyboard + Touch, Keyboard + Mouse + Touch, Keyboard + Mouse + Joystick, Serial + Keyboard + Mouse + Joystick, Flight Sim Controls, or Flight Sim Controls + Joystick. For the ones with Keyboard, you can choose the keyboard layout (
Tools >
Keyboard layout) from a selection of some common ones. Then, your sketch only needs to examine the input pin states on the microcontroller, perhaps with software debouncing, and the exposed
Keyboard object to emit the keypresses – there is even a "send a sequence of keypresses" available which implements the necessary interstroke delays. It's a pity Teensy LC are no longer available; they were perfect for this kind of purpose –– similar to how ATmega32U4 and ATUSB1286 were earlier, among Atmel AVRs, both in hardware, price, and ease of development using free open source tools. (In Teensies, only the bootloader (on the AVR chip in Teensy 2.x, on the dedicated boot chip in Teensy LC/3.x/4.x) and board files are proprietary; the Arduino core and support files are open source, and even
key schematics are available. One can even design their own Teensy-like boards, as PJRC is happy to sell pre-programmed Teensy
bootloader chips, and thus take advantage of the Arduino+Teensyduino software environment for rapid development and experimentation.)
All human interface device inputs are handled by the
Linux Input Subsystem. There are Linux kernel drivers for e.g. GPIO pin matrix keypads that do the same, but you can also use the
uinput module to synthesize a human interface device using userspace software (privileges are only needed during opening of the
/dev/uinput device and instantiating the new device node); the synthetic
uinput devices behave exactly like USB HID hardware devices would. An application could theoretically distinguish between the two, but no userspace program I've ever seen the sources of actually try to, at all: there is no need to. It all Just Works.
On the userspace application side, LinuxCNC uses the input event character devices directly (see e.g.
lib/python/linux_event.py in the
LinuxCNC sources).
This means that you need to implement either a real USB HID device, a PS/2 keyboard device (if the host exposes a PS/2 hardware interface), a kernel driver (for e.g. matrix keypads), or use a Linux
uinput userspace service/daemon to convert other input formats like serial ports to HID events.
The Input Subsystem splits HID devices into generic event devices, mice, and joysticks. I recommend using the
Event userspace API, and therefore the
/dev/input/eventN character devices (as named by
/sys/class/input/eventN/device/name pseudofiles). Normally, all keyboard devices' events are forwarded to the currently active TTY by the kernel. For multi-seat support, a session manager grabs the corresponding keyboard and mouse character devices: if you use the
EVIOCGRAB ioctl on the opened file descriptor, reading the events from the character device consumes them and causes them to no longer be forwarded/copied. Thus, if you want, you can easily write an userspace service that monitors HID devices, and grabs specific ones, consuming/translating/mapping their events to synthetic ones emitted via
uinput, and thus showing up as a completely new hardware HID device. While this is easy, using a microcontroller with native USB support and USB HID library support is obviously easier.
I personally am interested in using the cheap WCH CH552G/CH554G (currently < 0.90€ in singles at LCSC, USD $0.94 at JLCPCB assembly) for USB HID device experiments. One can use
ch55xduino Arduino add-on to program it in C (via the free and open source
SDCC C compiler), and uploaded using Python, pyusb, and the chprog.py tool included in ch55xduino. There are other options too though, including for PlatformIO and even bare hardware development, mostly using SDCC C compiler since the MCU is based on MCS51 (enhanced 8051). Not only are the CH55x MCU's cheap, but they are easy to use, too, because they don't need an external crystal for USB 1.1 Low-Speed (1 Mbit/s) or Full-Speed (12 Mbit/s) communications; only a couple of 100nF/0.1µF bypass capacitors and maybe a 1-47µF bulk bypass capacitor, and optionally a switch and a 10kOhm resistor for switching it to bootloader mode when connecting to a host for reflashing the firmware using the built-in USB bootloader. Very, very easy!
If CH55x for HID interests you, I recommend you check out Stefan Wagner's (wagiminator)
CH552G MacroPad Plus, which has a rotary encoder with 12 programmable RGB LEDs, and 6 buttons with a programmable RGB LED each. Both the hardware design files and the software needed are licensed under
CC-BY-SA 3.0, making it perfect for learning from.
I use Linux myself, and developing and experimenting with this kind of device is real easy for me. For testing a new USB HID device, all I need to do is program it and plug it in, and it should Just Work. I can use
evtest to examine the events it generates, or Wireshark (or similar tools) to examine the exact USB HID packets it generates (and their timing). As the Linux input subsystem was designed along the same lines as USB HID events, I've found it very easy to deal with both, with only
timing causing occasional issues on the microcontroller side (if and only if the "keyboard" interface does not examine and enforce the packet frequency set by the host) – an USB HID device cannot inject keypresses faster than a superfast robot tapping on an actual keyboard would.
The same applies to most TV boxes, routers, NAS, and other appliances, as they too tend to be based on either Linux or Android (Android kernel being a derivative of the Linux kernel), and use the exact same input subsystem. Very useful for novel gadgets, for example if you create your own Kodi TV box, and want to experiment with gesture control of your TV. I'm happy to provide more detailed information (like
this, still perfectly valid over a decade later; this is
that stable of an interface), even down to examples (like
this one, which 2D transforms touch events and virtual button presses to input events). However, I'm of very little to no use if you develop on Windows, unless you use a virtual machine running some flavour of Linux in it, for example Linux Mint. (WSL/WSL2 is a translation layer, and is not sufficient/compatible enough for Linux-specific systems-level programming like this.)
Apologies for the long post: I had lots to say.