从输入事件中读取条形码(linux,c)

我有一个小程序从/ dev / input / event4读取条形码.
这是代码:

#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;
}

我现在用在线生成器(http://www.barcode-generator.de/V2/de/index.jsp)创建了一些条形码.条形码是:
123456789和1234567890

扫描条形码时我的程序输出是:

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

为123456789

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

对于1234567890

因此,无法正确识别10位数条形码.

代码:28表示这是RETURN / ENTER,这是条形码的内部终止符,因此直接来自扫描仪.

有谁能告诉我为什么?也许代码有问题?

再见,安德烈

解决方法:

你应该只在read()返回== sizeof ev时考虑事件,因为我们在这里读取输入设备.如果它返回零,则表示没有更多事件即将发布(可能是设备已分离?).如果返回-1,请检查errno.如果read()返回任何其他值,则内核驱动程序已经消失了,你可以认为它是一个致命的错误.

errno == EINTR是正常的(当信号传递时发生),它本身不是错误.它不应该发生在这里,但忽略它(将其视为打嗝,而不是错误)是非常安全的.

errno == EAGAIN在open()标志中使用O_NONBLOCK时发生,并且还没有可用的新事件.

这里绝对没有理由使用O_NONBLOCK.所有这一切都会导致代码浪费CPU周期,从read()调用每秒返回数万次,只返回-1并使用errno == EAGAIN.只需删除它,以便read()只是等到新事件到来,然后返回它.

请参阅我对input_event structure description问题的回答.

总之,对于ev_type == 1 == EV_KEY:

> ev_value == 0:密钥释放(密钥向上)
> ev_value == 1:按下键(按键)
> ev_value == 2:autorepeat(自动重复键)
> 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

所提供设备的按键实际上是1 2 3 4 5 6 7 8 9输入.

(请注意,您只显示了密钥发布事件;对于每个ev_code,您实际上应该看到两个,一个ev_value == 1,后面跟一个ev_value == 0.)

我试过的一个中国人非常好,虽然很便宜.它有一个带有几个条形码的手册,包括一些在条形码格式(和数字位数)之间切换的条形码.我依稀记得使用两个条形码切换到另一种模式,并使用最小音量来发出哔哔声.即使在分离后它似乎也保留了设置.

这是我用来读取条形码的实现方式的示例.我显然将条形码读取部分拆分为单独的文件.

以下代码专用于公共领域(根据CC0许可),因此您可以随意使用它.没有任何保证,所以不要因为任何破损而责怪我. (任何错误修复都是受欢迎的;如果报告,我会检查并包含在下面的代码中.我建议在下面添加评论;我每隔几天左右就会阅读所有评论.)

头文件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 */

以下barcode.c文件中的实现目前只接受数字(0到1),但添加任何其他必要的键(如KEY_A到KEY_Z)应该是微不足道的.目前的一个忽略了移位,控制等等,因为据我所知,它们不是由任何扫描仪提供的.它使用SIGRTMAX-0实时信号和每个条形码设备的自定义计时器来读取条形码,因此您需要将其链接到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;
}

这是一个示例程序example.c.你提供输入事件设备(我建议在/ dev / input / by-id /或/ dev / input / by-path /中使用符号链接,如果你的udev提供那些,因为事件设备索引可能在内核版本和硬件启动),以及您愿意等待/直到下一个条形码的最长持续时间.

#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;
}

如您所见,它只是将条形码打印到标准输出(以及任何错误消息和警告标准错误).要编译它,我建议使用以下Makefile(缩进必须使用Tab,而不是空格):

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

要编译,请创建上面列出的四个文件,然后运行

make clean example

以跑步为例

./example /dev/input/event4 5.0

将从/ dev / input / event4读取条形码,但将在Ctrl C(INT信号),HUP信号,TERM信号或5秒内没有条形码出现时退出.

请注意,如果在5秒内只读取部分条形码,我们会得到部分条件(并且可以尝试读取其余部分),但上面的示例程序忽略了部分,并且只显示了超时.

有问题吗?

上一篇:PHP 生成条形码&二维码


下一篇:c# – 如何在Windows Phone 8.1上使用Zxing创建QR码图像