2

So there have been a few questions on this topic but without really high-quality answers.

My problem is the following: I have an embedded application running on a Raspberry Pi, which is started via init.d. The entire setup does not have screen and is generally supposed to be working with the network disabled in production use (bc it will go into an environment where interference from the WiFi / Bluetooth would possibly be problematic).

I want to use a generic USB keyboard / number pad as an input device for configuration and troubleshooting purposes. Naturally, I can not just read from cin, because my program isn't running on a terminal (and, in fact, there is no terminal).

How can I do this, preferably on a level where i do not have to worry about details of the keyboard layout and / or number of input devices plugged in?

edit My likely workaround will be something involving autologin and a .profile script. Still, if anybody has a solution to this, it would be appreciated.

  • Devices have device drivers which the operating system uses to interface those devices. Then you can open the device on linjx based systems then read and write those devices. – Bayleef May 27 '20 at 14:38
  • I assume you are suggesting accessing /dev/input0 et al as a file stream? I was hoping for something a bit more sophisticated than that. –  May 27 '20 at 14:39
  • 1
    I had to do exactly this on a BeagleBone (running Debian) this weekend. Only way I found to do it was reading `/dev/hidraw0` and decoding the key codes myself; they seem to come in 6-byte packets, with the shift/alt/ctl modifier as the first byte, then a reserved byte, then the codes for the keys up to 4. You get a packet with all zeros for the key-up event. Surely there's a way to interpose a driver, but I haven't found it yet. – Steve Friedl May 27 '20 at 14:41
  • I mean there isn't a library for everything. That's how you have to do it on a linux os. That is not a straight embedded system so you have to deal with the abstractions the raspberry pi and linux give you. You have to use system calls to interact with the devices. Write your own code to abstract the access. – Bayleef May 27 '20 at 14:56
  • Or create a super funky device driver that handles stuff like that for you and create your own kernel hooks. Idk – Bayleef May 27 '20 at 14:57
  • 1
    Look, I'm here for helpful answers, not for passive agression, Bailey. –  May 27 '20 at 15:08
  • @SteveFriedl, I have tried this approach but in my case the data does not follow the 6-byte format you described. It would seem that the output format varies between devices, which is a problem for my usecase. –  May 27 '20 at 15:30
  • You can sense passive aggression through text? I was just telling you what you need to do. – Bayleef May 27 '20 at 16:01
  • 1
    @BaileyKocin - Saying "Go find a library" or "Just write some code" is not really responsive to the question when it seems likely that somebody else has solved this already. @Taschi - I just did some looking at `libhidapi` (on Debian) and it does offer interesting support for device numeration, but their low-level `hid_read()` function doesn't appear to do more than read the same 6 bytes (or whatever) I was getting by reading `/dev/hidraw`. Still looking for other layers. – Steve Friedl May 27 '20 at 17:51
  • 1
    What im trying to say is that with linux C embedded code it's like shooting for a needle in a haystack. You are really better of writing your own code on top of the device drivers. It's probably alot easier then searching for 4 days straight. – Bayleef May 27 '20 at 17:58
  • 1
    And as for @Taschi. I gave you my intent but I can't control your perception. I gave you one of many solutions to your question. – Bayleef May 27 '20 at 18:00
  • 1
    @SteveFriedl Did you look at `libevdev` and `evtest` to see if they handle your hid device more easily through `/dev/input/event...`? – meuh May 27 '20 at 18:16

1 Answers1

2

It seems like @meuh wins the cookie for the best advice: libevdev is exactly on point.

I found this answer that solved a related problem of binding a key (ALT-X) to automatically launch a program, and the overall structure was really easy to adapt (my modified code is below).

In each struct input_event you get, you look for ev.type == EV_KEY to pick off keyboard events (as opposed to mouse or other events), ev.code contains the code for the key (KEY_UP, KEY_0, KEY_KP5, KEY_BACKSPACE, etc.). I'm testing with just a number pad so I don't get the shifts or alt or the like, but I suspect it's straightforward.

You also look at ev.value, which can be:

  • EV_KEY - key down
  • EV_REL - key repeat values (optional, can be more than one)
  • EV_SYN - key up

I imagine for some applications you could just ignore all except for the EV_SYN to capture the key-up event; that's what I'll be doing.

$ sudo ./evtest
Device /dev/input/event1 is open and associated w/ libevent
KEY: Value=EV_KEY; Code=KEY_KP7    <-- KP = keypad
KEY: Value=EV_SYN; Code=KEY_KP7
KEY: Value=EV_KEY; Code=KEY_KP8
KEY: Value=EV_SYN; Code=KEY_KP8
KEY: Value=EV_KEY; Code=KEY_KP9
KEY: Value=EV_SYN; Code=KEY_KP9

Note that the key values are not ASCII and are also not traditional keyboard scan codes - these are a whole new namespace, and there's probably some other abstraction layer that translates to regular ASCII, but I haven't looked for it as the KEY_* codes are fine for my application. But it's way, way better than the lousy /dev/hidraw0 mechanism I was using before.

It does require root permission, which makes sense because otherwise your user-mode program could lay in wait for the superuser to login on the console, snagging their password. For an embedded application I'm sure this is not going to be an issue.

Thank you, @meuh for the great tip. And I didn't even have to write a device driver!

The code below runs on a BeagleBone running Debian Buster.

// hack test for working with events
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <err.h>
#include <libevdev-1.0/libevdev/libevdev.h>

#define COUNTOF(x)  (int) ( ( sizeof(x) / sizeof((x)[0]) ) )

static void setupKeyCodes(void);
static const char *printableEventType(int t);
static const char *keycodes[64 * 1024] = { 0 }; // hack

int main(void) {

    setupKeyCodes();

    const char *eventDevice = "/dev/input/event1";

    const int fd = open(eventDevice, O_RDONLY | O_NONBLOCK);

    if (fd < 0) errx(EXIT_FAILURE, "ERROR: cannot open device %s [%s]", eventDevice, strerror(errno));

    struct libevdev *dev;

    int err = libevdev_new_from_fd(fd, &dev);

    if (err < 0) errx(EXIT_FAILURE, "ERROR: cannot associate event device [%s]", strerror(-err));

    printf("Device %s is open and associated w/ libevent\n", eventDevice);
    do {
        struct input_event ev;

        err = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);

        if (err == 0 && ev.type == EV_KEY)
        {
            printf("KEY: Value=%s; Code=%s\n",
                printableEventType(ev.value),
                keycodes[ev.code]);
        }
    } while (err == 1 || err == 0 || err == -EAGAIN);

    return 0;
}

// HACK: populate the whole array of possible keycodes with their strings
// so we can see what we're reading.

static void setupKeyCodes(void)
{
    for (int i = 0; i < COUNTOF(keycodes); i++)
        keycodes[i] = "-unknown-";

    // these from /usr/include/linux/input-event-codes.h

    keycodes[KEY_RESERVED] = "KEY_RESERVED";
    keycodes[KEY_ESC] = "KEY_ESC";
    keycodes[KEY_1] = "KEY_1";
    keycodes[KEY_2] = "KEY_2";
    keycodes[KEY_3] = "KEY_3";
    keycodes[KEY_4] = "KEY_4";
    keycodes[KEY_5] = "KEY_5";
    keycodes[KEY_6] = "KEY_6";
    keycodes[KEY_7] = "KEY_7";
    keycodes[KEY_8] = "KEY_8";
    keycodes[KEY_9] = "KEY_9";
    keycodes[KEY_0] = "KEY_0";
    // ... many many more
    keycodes[KEY_STOP_RECORD] = "KEY_STOP_RECORD";
    keycodes[KEY_PAUSE_RECORD] = "KEY_PAUSE_RECORD";
    keycodes[KEY_VOD] = "KEY_VOD";
    keycodes[KEY_UNMUTE] = "KEY_UNMUTE";
    keycodes[KEY_FASTREVERSE] = "KEY_FASTREVERSE";
    keycodes[KEY_SLOWREVERSE] = "KEY_SLOWREVERSE";
}

#define STRCASE(x)  case x: return #x

static const char *printableEventType(int t)
{
    switch (t)
    {
    STRCASE(EV_SYN);
    STRCASE(EV_KEY);
    STRCASE(EV_REL);
    STRCASE(EV_ABS);
    STRCASE(EV_MSC);
    STRCASE(EV_SW);
    STRCASE(EV_LED);
    STRCASE(EV_SND);
    STRCASE(EV_REP);
    STRCASE(EV_FF);
    STRCASE(EV_PWR);
    STRCASE(EV_FF_STATUS);
    default: return "-?-";
    }
}
Steve Friedl
  • 3,929
  • 1
  • 23
  • 30
  • 1
    Instead of writing your own `die()` function, you can use [`err()`](https://linux.die.net/man/3/err) from ``. – G. Sliepen May 27 '20 at 21:41
  • @G.Sliepen - well how about that! I've been using the same `die()` function for almost 40 years and it had never heard of the library version. Thank you. – Steve Friedl May 27 '20 at 21:43
  • This works for me, although there is still some fuzziness around the /dev/eventX files (my keyboard writes to event0, yours is apparently on event1). Nothing impossible to handle, though. –  May 28 '20 at 16:40
  • @Taschi - please check the link I got my info from - the example code there has really useful stuff re: enumeration and description of all the attached devices - this is how I figured out which device was the one I wanted. – Steve Friedl May 28 '20 at 16:43