Read barcodes from input-event (linux, c)

2019-01-15 04:38发布

问题:

I have a small program that read barcodes from /dev/input/event4. This is the code:

#include <sys/file.h>
#include <stdio.h>
#include <string.h>
#include <linux/input.h>

int main (int argc, char *argv[])
{
        struct input_event ev;
        int fd, rd;

        //Open Device
        if ((fd = open ("/dev/input/event4", O_RDONLY|O_NONBLOCK)) == -1){
                printf ("not a vaild device.\n");
                return -1;
        }

        while (1){

                memset((void*)&ev, 0, sizeof(ev));

                rd = read (fd, (void*)&ev, sizeof(ev));

                if (rd <= 0){
                        printf ("rd: %d\n", rd);
                        sleep(1);
                }

                if(rd>0 && ev.value==0 && ev.type==1){
                        printf("type: %d, code: %d, value: %d, rd: %d\n", ev.type, ev.code, ev.value, rd);
                }
        }

        return 0;
}

I have now created some barcodes with an online-generator (http://www.barcode-generator.de/V2/de/index.jsp). The barcodes are: 123456789 and 1234567890

The output of my programm when scanning the barcodes is:

type: 1, code: 2, value: 0, rd: 16
type: 1, code: 3, value: 0, rd: 16
type: 1, code: 4, value: 0, rd: 16
type: 1, code: 5, value: 0, rd: 16
type: 1, code: 6, value: 0, rd: 16
type: 1, code: 7, value: 0, rd: 16
type: 1, code: 8, value: 0, rd: 16
type: 1, code: 9, value: 0, rd: 16
type: 1, code: 10, value: 0, rd: 16
type: 1, code: 28, value: 0, rd: 16

for the 123456789

and

type: 1, code: 28, value: 0, rd: 16

for the 1234567890

So, the 10-digit-barcodes is not recognised correctly.

The code: 28 means this is an RETURN/ENTER, this is the internal terminator for a barcode, so this comes directly from the scanner.

Does anyone can tell my why ? Maybe there is something wrong with the code ?

Goodbye, Andre

回答1:

You should only consider the event when the read() returns == sizeof ev, because we're reading from an input device here. If it returns zero, it means no more events are forthcoming (maybe device detached?). If it returns -1, check errno. If read() returns any other value, the kernel driver has gone bonkers and you could consider it a fatal error.

errno == EINTR is normal (occurs when a signal is delivered), it is not an error per se. It shouldn't happen here, but ignoring it (treating it as just a hiccup, not an error) is quite safe.

errno == EAGAIN occurs when you used O_NONBLOCK in the open() flags, and there is no new event available yet.

There is absolutely no reason to use O_NONBLOCK here. All it does is causes your code to waste CPU cycles, returning tens of thousands of times per second from the read() call just to return -1 with errno == EAGAIN. Just drop it, so that the read() will simply wait until a new event arrives, and returns it.

See my answer to the input_event structure description question.

In summary, for ev_type == 1 == EV_KEY:

  • ev_value == 0: key released (key up)
  • ev_value == 1: key pressed (key down)
  • ev_value == 2: autorepeat (key automatically repeated)
  • ev_code == 1 == KEY_ESC
  • ev_code == 2 == KEY_1
  • ev_code == 3 == KEY_2
  • ev_code == 10 == KEY_9
  • ev_code == 11 == KEY_0
  • ev_code == 28 == KEY_ENTER

The keypresses the device provided are actually 1 2 3 4 5 6 7 8 9 Enter.

(Note that you only showed the key release events; you should actually see two, one with ev_value == 1, followed by one with ev_value == 0, for each ev_code.)

The one Chinese one I've tried was very nice, although dirt cheap. It had a manual with a few barcodes, including some to switch between barcode formats (and number of digits). I vaguely remember using two barcodes to switch to another mode, and to use the minimum volume for the beeps. It seemed to retain the settings even after being detached.


Here is an example of what kind of implementation I'd use to read barcodes. I'd obviously split the barcode reading part to a separate file.

The below code is dedicated to public domain (licensed under CC0), so feel free to use it in any way you wish. There is no guarantees of any kind, so don't blame me for any breakage. (Any bug fixes are welcome; if reported, I will check and include in the below code. I recommend adding a comment below; I do read all comments to my answers every couple of days or so.)

The header file barcode.h:

#ifndef   BARCODE_H
#define   BARCODE_H
#include <stdlib.h>
#include <signal.h>

/* This flags turns nonzero if any signal
 * installed with install_done is caught.
*/
extern volatile sig_atomic_t done;

/* Install signals that set 'done'.
*/
int install_done(const int signum);

/* Barcode device description.
 * Do not meddle with the internals;
 * this is here only to allow you
 * to allocate one statically.
*/
typedef struct {
    int             fd;
    volatile int    timeout;
    timer_t         timer;
} barcode_dev;

/* Close a barcode device.
 * Returns 0 if success, nonzero errno error code otherwise.
*/
int barcode_close(barcode_dev *const dev);

/* Open a barcode device.
 * Returns 0 if success, nonzero errno error code otherwise.
*/
int barcode_open(barcode_dev *const dev, const char *const device_path);

/* Read a barcode, but do not spend more than maximum_ms.
 * Returns the length of the barcode read.
 * (although at most length-1 characters are saved at the buffer,
 *  the total length of the barcode is returned.)
 * errno is always set; 0 if success, error code otherwise.
 * If the reading timed out, errno will be set to ETIMEDOUT.
*/
size_t barcode_read(barcode_dev *const dev,
                    char *const buffer, const size_t length,
                    const unsigned long maximum_ms);

#endif /* BARCODE_H */

The implementation in the following barcode.c file currently accepts only digits (0 through 1), but it should be trivial to add any other necessary keys (like KEY_A through KEY_Z). The current one ignores shift, control, et cetera, as they are not provided by any scanners as far as I know. It uses SIGRTMAX-0 realtime signal and a custom timer per barcode device to read barcodes, so you'll need to link it against librt (-lrt):

#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <linux/input.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

/* Link against the rt library; -lrt. */

#define UNUSED          __attribute__((unused))
#define TIMEOUT_SIGNAL  (SIGRTMAX-0)

/*
 * done - flag used to exit program at SIGINT, SIGTERM etc.
*/

volatile sig_atomic_t done = 0;

static void handle_done(int signum UNUSED)
{
    done = 1;
}

int install_done(const int signum)
{
    struct sigaction act;
    sigemptyset(&act.sa_mask);
    act.sa_handler = handle_done;
    act.sa_flags = 0;
    if (sigaction(signum, &act, NULL) == -1)
        return errno;
    return 0;
}

/*
 * Barcode input event device, and associated timeout timer.
*/

typedef struct {
    int             fd;
    volatile int    timeout;
    timer_t         timer;
} barcode_dev;

static void handle_timeout(int signum UNUSED, siginfo_t *info, void *context UNUSED)
{
    if (info && info->si_code == SI_TIMER && info->si_value.sival_ptr)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
        __atomic_add_fetch((int *)info->si_value.sival_ptr, 1, __ATOMIC_SEQ_CST);
#else
        __sync_add_and_fetch((int *)info->si_value.sival_ptr, 1);
#endif
}

static int install_timeouts(void)
{
    static int installed = 0;

    if (!installed) {
        struct sigaction act;
        sigemptyset(&act.sa_mask);
        act.sa_sigaction = handle_timeout;
        act.sa_flags = SA_SIGINFO;
        if (sigaction(TIMEOUT_SIGNAL, &act, NULL) == -1)
            return errno;
        installed = 1;        
    }

    return 0;
}

int barcode_close(barcode_dev *const dev)
{
    int retval = 0;

    if (!dev)
        return 0;

    if (dev->fd != -1)
        if (close(dev->fd) == -1)
            retval = errno;

    dev->fd = -1;

    if (dev->timer)
        if (timer_delete(dev->timer) == -1)
            if (!retval)
                retval = errno;

    dev->timer = (timer_t)0;

    /* Handle all pending TIMEOUT_SIGNALs */
    while (1) {
        struct timespec t;
        siginfo_t info;
        sigset_t s;

        t.tv_sec = (time_t)0;
        t.tv_nsec = 0L;
        sigemptyset(&s);

        if (sigtimedwait(&s, &info, &t) != TIMEOUT_SIGNAL)
            break;

        if (info.si_code != SI_TIMER || !info.si_value.sival_ptr)
            continue;

#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
        __atomic_add_fetch((int *)info.si_value.sival_ptr, 1, __ATOMIC_SEQ_CST);
#else
        __sync_add_and_fetch((int *)info.si_value.sival_ptr, 1);
#endif
    }

    return errno = retval;
}

int barcode_open(barcode_dev *const dev, const char *const device_path)
{
    struct sigevent event;
    int fd;

    if (!dev)
        return errno = EINVAL;

    dev->fd = -1;
    dev->timeout = -1;
    dev->timer = (timer_t)0;

    if (!device_path || !*device_path)
        return errno = EINVAL;

    if (install_timeouts())
        return errno;

    do {
        fd = open(device_path, O_RDONLY | O_NOCTTY | O_CLOEXEC);
    } while (fd == -1 && errno == EINTR);
    if (fd == -1)
        return errno;

    errno = 0;
    if (ioctl(fd, EVIOCGRAB, 1)) {
        const int saved_errno = errno;
        close(fd);
        return errno = (saved_errno) ? errno : EACCES;
    }

    dev->fd = fd;

    memset(&event, 0, sizeof event);
    event.sigev_notify = SIGEV_SIGNAL;
    event.sigev_signo = TIMEOUT_SIGNAL;
    event.sigev_value.sival_ptr = (void *)&(dev->timeout);
    if (timer_create(CLOCK_REALTIME, &event, &dev->timer) == -1) {
        const int saved_errno = errno;
        close(fd);
        return errno = (saved_errno) ? errno : EMFILE;
    }

    return errno = 0;
}

size_t barcode_read(barcode_dev *const dev,
                    char *const buffer, const size_t length,
                    const unsigned long maximum_ms)
{
    struct itimerspec it;
    size_t len = 0;
    int status = ETIMEDOUT;

    if (!dev || !buffer || length < 2 || maximum_ms < 1UL) {
        errno = EINVAL;
        return (size_t)0;
    }

    /* Initial timeout. */
    it.it_value.tv_sec = maximum_ms / 1000UL;
    it.it_value.tv_nsec = (maximum_ms % 1000UL) * 1000000L;

    /* After elapsing, fire every 10 ms. */
    it.it_interval.tv_sec = 0;
    it.it_interval.tv_nsec = 10000000L;

    if (timer_settime(dev->timer, 0, &it, NULL) == -1)
        return (size_t)0;

    /* Because of the repeated elapsing, it is safe to
     * clear the timeout flag here. */
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR >= 7)
    __atomic_store_n((int *)&(dev->timeout), 0, __ATOMIC_SEQ_CST);
#else
    __sync_fetch_and_and((int *)&(dev->timeout), 0);
#endif

    while (!dev->timeout) {
        struct input_event ev;
        ssize_t n;
        int digit;

        n = read(dev->fd, &ev, sizeof ev);
        if (n == (ssize_t)-1) {
            if (errno == EINTR)
                continue;
            status = errno;
            break;

        } else
        if (n == sizeof ev) {

            /* We consider only key presses and autorepeats. */
            if (ev.type != EV_KEY || (ev.value != 1 && ev.value != 2))
                continue;

            switch (ev.code) {
            case KEY_0: digit = '0'; break;
            case KEY_1: digit = '1'; break;
            case KEY_2: digit = '2'; break;
            case KEY_3: digit = '3'; break;
            case KEY_4: digit = '4'; break;
            case KEY_5: digit = '5'; break;
            case KEY_6: digit = '6'; break;
            case KEY_7: digit = '7'; break;
            case KEY_8: digit = '8'; break;
            case KEY_9: digit = '9'; break;
            default:    digit = '\0';
            }

            /* Non-digit key ends the code, except at beginning of code. */
            if (digit == '\0') {
                if (!len)
                    continue;
                status = 0;
                break;
            }

            if (len < length)
                buffer[len] = digit;
            len++;

            continue;

        } else
        if (n == (ssize_t)0) {
            status = ENOENT;
            break;                

        } else {
            status = EIO;
            break;
        }
    }

    /* Add terminator character to buffer. */
    if (len + 1 < length)
        buffer[len + 1] = '\0';
    else
        buffer[length - 1] = '\0';

    /* Cancel timeout. */
    it.it_value.tv_sec = 0;
    it.it_value.tv_nsec = 0;
    it.it_interval.tv_sec = 0;
    it.it_interval.tv_nsec = 0L;
    (void)timer_settime(dev->timer, 0, &it, NULL);

    errno = status;
    return len;
}

Here is an example program, example.c. You supply the input event device (I suggest using a symlink in /dev/input/by-id/ or /dev/input/by-path/ if your udev provides those, as event device indexes may not be stable across kernel versions and hardware boots), and the maximum duration you're willing to wait for/until next barcode.

#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include "barcode.h"

#define BARCODE_MAXLEN  1023

int main(int argc, char *argv[])
{
    barcode_dev    dev;
    unsigned long  ms;
    int            status, exitcode;

    if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s INPUT-EVENT-DEVICE IDLE-TIMEOUT\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program reads barcodes from INPUT-EVENT-DEVICE,\n");
        fprintf(stderr, "waiting at most IDLE-TIMEOUT seconds for a new barcode.\n");
        fprintf(stderr, "The INPUT-EVENT-DEVICE is grabbed, the digits do not appear as\n");
        fprintf(stderr, "inputs in the machine.\n");
        fprintf(stderr, "You can at any time end the program by sending it a\n");
        fprintf(stderr, "SIGINT (Ctrl+C), SIGHUP, or SIGTERM signal.\n");
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    if (install_done(SIGINT) ||
        install_done(SIGHUP) ||
        install_done(SIGTERM)) {
        fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    {
        double value, check;
        char dummy;
        if (sscanf(argv[2], " %lf %c", &value, &dummy) != 1 || value < 0.001) {
            fprintf(stderr, "%s: Invalid idle timeout value (in seconds).\n", argv[2]);
            return EXIT_FAILURE;
        }
        ms = (unsigned long)(value * 1000.0);
        check = (double)ms / 1000.0;

        if (value < check - 0.001 || value > check + 0.001 || ms < 1UL) {
            fprintf(stderr, "%s: Idle timeout is too long.\n", argv[2]);
            return EXIT_FAILURE;
        }
    }

    if (barcode_open(&dev, argv[1])) {
        fprintf(stderr, "%s: Cannot open barcode input event device: %s.\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    }

    while (1) {
        char    code[BARCODE_MAXLEN + 1];
        size_t  len;

        if (done) {
            status = EINTR;
            break;
        }

        len = barcode_read(&dev, code, sizeof code, ms);
        if (errno) {
            status = errno;
            break;
        }
        if (len < (size_t)1) {
            status = ETIMEDOUT;
            break;
        }

        printf("%zu-digit barcode: %s\n", len, code);
        fflush(stdout);
    }

    if (status == EINTR) {
        fprintf(stderr, "Signaled to exit. Complying.\n");
        fflush(stderr);
        exitcode = EXIT_SUCCESS;
    } else
    if (status == ETIMEDOUT) {
        fprintf(stderr, "Timed out, no more barcodes.\n");
        fflush(stderr);
        exitcode = EXIT_SUCCESS;
    } else {
        fprintf(stderr, "Error reading input event device %s: %s.\n", argv[1], strerror(status));
        fflush(stderr);
        exitcode = EXIT_FAILURE;
    }

    if (barcode_close(&dev)) {
        fprintf(stderr, "Warning: Error closing input event device %s: %s.\n", argv[1], strerror(errno));
        fflush(stderr);
        exitcode = EXIT_FAILURE;
    }

    return exitcode;
}

As you can see, it just prints the barcodes to standard output (and any error messages and warnings to standard error). To compile it, I recommend using the following Makefile (indentation must be using Tab, not spaces):

CC      := gcc
CFLAGS  := -Wall -Wextra -O2
LDFLAGS := -lrt

.PHONY: all clean

all: clean example

clean:
    rm -f example *.o

%.o: %.c
    $(CC) $(CFLAGS) -c $^

example: example.o barcode.o
    $(CC) $(CFLAGS) $^ $(LDFLAGS) -o example

To compile, create the four files listed above, then run

make clean example

Running for example

./example /dev/input/event4 5.0

will read barcodes from /dev/input/event4, but will exit at Ctrl+C (INT signal), HUP signal, TERM signal, or if no barcode appears within 5 seconds.

Note that if only a partial barcode is read within that 5 seconds, we do get that partial part (and could just try and read the rest of it), but the above example program ignores the partial, and only shows the timeout.

Questions?