从输入事件中读取条形码 (linux, c)
Read barcodes from input-event (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()
returns == sizeof ev
时的事件,因为我们在这里从输入设备读取。如果它 return 为零,则意味着没有更多事件即将发生(也许设备已分离?)。如果它 returns -1
,检查 errno
。如果 read()
return 是任何其他值,则内核驱动程序出现故障,您可以将其视为致命错误。
errno == EINTR
是正常的(下发信号时出现),本身不是错误。它不应该发生在这里,但忽略它(将其视为打嗝,而不是错误)是非常安全的。
errno == EAGAIN
当您在 open()
标志中使用 O_NONBLOCK
时出现,并且还没有可用的新事件。
这里绝对没有理由使用O_NONBLOCK
。它所做的只是导致您的代码浪费 CPU 周期,从 read()
调用到 return -1
每秒 returning 数万次errno == EAGAIN
。只需放下它,这样 read()
就会简单地等到新事件到达,然后 return 处理它。
查看我对 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
设备提供的按键实际上是123 4 5 6 7 8 9 输入.
(请注意,您只显示了按键释放事件;您实际上应该看到两个,一个带有 ev_value == 1
,然后一个带有 ev_value == 0
,每个 ev_code
。)
我试过的一个中国人非常好,虽然便宜得要命。它有一个带有一些条形码的手册,包括一些用于在条形码格式(和位数)之间切换的条形码。我依稀记得使用两个条形码切换到另一种模式,并使用最小音量的哔哔声。分离后好像还保留着设置
这里是我用来读取条形码的实现类型的示例。我显然会将条形码读取部分拆分到一个单独的文件中。
以下代码专用于 public 域(在 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
应该是微不足道的).目前的版本忽略了 shift、control 等,因为据我所知,任何扫描仪都没有提供它们。它使用 SIGRTMAX-0
实时信号和每个条码设备的自定义计时器来读取条码,因此您需要 link 它反对 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 = '[=11=]';
}
/* Non-digit key ends the code, except at beginning of code. */
if (digit == '[=11=]') {
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] = '[=11=]';
else
buffer[length - 1] = '[=11=]';
/* 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/
中使用 symlink 如果您的 udev
提供这些,因为事件设备索引在内核版本中可能不稳定和硬件启动),以及您愿意等待的最长时间 for/until 下一个条形码。
#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 秒内只读取了部分条码,我们确实得到了该部分(并且可以尝试读取其余部分),但上面的示例程序忽略了部分,只显示超时。
有问题吗?
我有一个小程序可以从 /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()
returns == sizeof ev
时的事件,因为我们在这里从输入设备读取。如果它 return 为零,则意味着没有更多事件即将发生(也许设备已分离?)。如果它 returns -1
,检查 errno
。如果 read()
return 是任何其他值,则内核驱动程序出现故障,您可以将其视为致命错误。
errno == EINTR
是正常的(下发信号时出现),本身不是错误。它不应该发生在这里,但忽略它(将其视为打嗝,而不是错误)是非常安全的。
errno == EAGAIN
当您在 open()
标志中使用 O_NONBLOCK
时出现,并且还没有可用的新事件。
这里绝对没有理由使用O_NONBLOCK
。它所做的只是导致您的代码浪费 CPU 周期,从 read()
调用到 return -1
每秒 returning 数万次errno == EAGAIN
。只需放下它,这样 read()
就会简单地等到新事件到达,然后 return 处理它。
查看我对 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
设备提供的按键实际上是123 4 5 6 7 8 9 输入.
(请注意,您只显示了按键释放事件;您实际上应该看到两个,一个带有 ev_value == 1
,然后一个带有 ev_value == 0
,每个 ev_code
。)
我试过的一个中国人非常好,虽然便宜得要命。它有一个带有一些条形码的手册,包括一些用于在条形码格式(和位数)之间切换的条形码。我依稀记得使用两个条形码切换到另一种模式,并使用最小音量的哔哔声。分离后好像还保留着设置
这里是我用来读取条形码的实现类型的示例。我显然会将条形码读取部分拆分到一个单独的文件中。
以下代码专用于 public 域(在 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
应该是微不足道的).目前的版本忽略了 shift、control 等,因为据我所知,任何扫描仪都没有提供它们。它使用 SIGRTMAX-0
实时信号和每个条码设备的自定义计时器来读取条码,因此您需要 link 它反对 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 = '[=11=]';
}
/* Non-digit key ends the code, except at beginning of code. */
if (digit == '[=11=]') {
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] = '[=11=]';
else
buffer[length - 1] = '[=11=]';
/* 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/
中使用 symlink 如果您的 udev
提供这些,因为事件设备索引在内核版本中可能不稳定和硬件启动),以及您愿意等待的最长时间 for/until 下一个条形码。
#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 秒内只读取了部分条码,我们确实得到了该部分(并且可以尝试读取其余部分),但上面的示例程序忽略了部分,只显示超时。
有问题吗?