c - 从输入事件中读取条形码(Linux,C)

标签 c linux device barcode barcode-scanner

我有一个小程序,可以从/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

扫描条形码时,我的programm输出为:
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是正常的(在传递信号时发生),它本身不是错误。它不应该在这里发生,但是忽略它(将其视为打ic,而不是错误)是非常安全的。

当您在errno == EAGAIN标志中使用O_NONBLOCK时,就会发生open(),并且尚无新事件可用。

这里绝对没有理由使用O_NONBLOCK。这样做只会导致您的代码浪费CPU周期,每秒从read()调用返回数万次,只是返回带有-1errno == EAGAIN。只需将其放下即可,以便read()只需等待一个新事件到达并返回它即可。

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

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

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

    (请注意,您只显示了按键释放事件;对于每个ev_value == 1,您实际上应该看到两个,一个带有ev_value == 0,然后一个带有ev_code。)

    我尝试过的一个中国人很好,尽管便宜。它具有一本手册,其中包含一些条形码,包括一些可在条形码格式(和位数)之间切换的手册。我隐约记得使用两个条形码切换到另一种模式,并使用最小音量来发出哔声。即使分离,它似乎仍保留设置。

    这是我将使用哪种实现方式读取条形码的示例。我显然将条形码读取部分拆分为一个单独的文件。

    以下代码专用于公共(public) Realm (已在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_AKEY_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/中使用符号链接(symbolic link),如果您的udev提供了这些符号链接(symbolic link),因为事件设备索引可能在内核版本和硬件启动之间不稳定),以及您愿意等待的最大持续时间/直到下一个条形码。
    #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秒钟之内只读取了部分条形码,我们确实会得到该部分(可以尝试读取其余部分),但是上面的示例程序将忽略该部分,仅显示超时。

    有什么问题吗

    关于c - 从输入事件中读取条形码(Linux,C),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29942421/

    相关文章:

    c - C 中的数组语法和指针

    linux - Bash - 在没有文件的情况下使用 AT 命令

    android - 针对不同设备应用分辨率

    iphone - 如何通过安装 .app(二进制)文件在设备上测试应用程序? (不使用 Xcode 进行测试)

    c - C 中的冒泡排序错误代码! [Deitel C 第 6 版]

    c++ - 操作系统开发。如何开始?

    java - 创建c linux库的问题

    linux - jiffies.h 未知

    linux - 自定义脚本 echo -e 参数

    android - 如何在多个设备上运行 logcat?