Initial commit for ZMQ barcode service
This commit is contained in:
parent
a3a73ea1e8
commit
9e3b4d023c
13
barcode_service/Makefile
Normal file
13
barcode_service/Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
CC=gcc
|
||||
CFLAGS=-O2 -Wall -Wextra
|
||||
LDFLAGS=-lzmq -lczmq
|
||||
EXE_NAME=barcode_service
|
||||
|
||||
all: $(EXE_NAME)
|
||||
|
||||
$(EXE_NAME): barcode_service.c
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f $(EXE_NAME)
|
271
barcode_service/barcode_service.c
Normal file
271
barcode_service/barcode_service.c
Normal file
@ -0,0 +1,271 @@
|
||||
#include <argp.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/input-event-codes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <czmq.h>
|
||||
|
||||
#include "barcode_service.h"
|
||||
|
||||
#define DEV_ID_PREFIX "/dev/input/by-id/"
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
zsock_t *zsock;
|
||||
args cli_args = {};
|
||||
barcode barcode;
|
||||
int input_fd;
|
||||
|
||||
/* Read command line arguments. */
|
||||
if (argp_parse(&argp, argc, argv, 0, NULL, &cli_args) != 0)
|
||||
{
|
||||
fprintf(stderr, "Could not read command line arguments!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Acquire input device event fd. */
|
||||
const int path_size = strlen(DEV_ID_PREFIX) + strlen(cli_args.dev_id);
|
||||
char *path = Calloc(path_size + 1);
|
||||
strcpy(path, DEV_ID_PREFIX);
|
||||
strcat(path, cli_args.dev_id);
|
||||
if ((input_fd = open(path, O_RDONLY)) < 0)
|
||||
{
|
||||
fprintf(stderr, "Could not open input device at %s!\n", path);
|
||||
exit(input_fd);
|
||||
}
|
||||
free(path);
|
||||
|
||||
/* Open ZMQ 'PUB' socket. */
|
||||
zsock = zsock_new_pub((const char *) cli_args.zmq_endpoint);
|
||||
if (zsock == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to bind new ZMQ socket!");
|
||||
}
|
||||
|
||||
/* Acquire and send barcodes through the ZMQ socket. */
|
||||
while (true)
|
||||
{
|
||||
barcode = await_next_barcode(input_fd);
|
||||
if (zsock_send(zsock, "s", barcode) < 0)
|
||||
{
|
||||
fprintf(stderr, "Sending barcode failed!\n");
|
||||
}
|
||||
free(barcode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from an input device file descriptor given by fd. Return the barcode
|
||||
* that was read. The barcode will need to be returned to the heap.
|
||||
*/
|
||||
static barcode
|
||||
await_next_barcode(int fd)
|
||||
{
|
||||
char c;
|
||||
barcode barcode;
|
||||
ll_string *ll_barcode = NULL;
|
||||
while ((c = decode_next_event(fd)) != '\n')
|
||||
{
|
||||
ll_string_append(ll_barcode, c);
|
||||
}
|
||||
barcode = ll_string_to_c_string(ll_barcode);
|
||||
ll_string_free(ll_barcode);
|
||||
return barcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an input event from the file descriptor.
|
||||
* If successful, return a char cast to an int containing an interpretation
|
||||
* of the character read, in lower case.
|
||||
* Not all input events are as straightforward as 'key x was depressed'. To this
|
||||
* end, this function will block and wait for the next input event until it sees
|
||||
* a key being pressed.
|
||||
*/
|
||||
static char
|
||||
decode_next_event(int fd)
|
||||
{
|
||||
struct input_event event;
|
||||
while (true)
|
||||
{
|
||||
/*
|
||||
* Read the next event.
|
||||
*/
|
||||
ssize_t n_read = read(fd, &event, sizeof(struct input_event));
|
||||
if (n_read != sizeof(struct input_event)) {
|
||||
fprintf(stderr, "Read interrupted!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we received a keypress event, decode the keycode responsible.
|
||||
*/
|
||||
if (event.type == EV_KEY) {
|
||||
switch (event.code) {
|
||||
#define DECODE_KEY(key_name, decode_char) case (KEY_##key_name): return decode_char
|
||||
DECODE_KEY(ENTER, '\n');
|
||||
DECODE_KEY(0, '0');
|
||||
DECODE_KEY(1, '1');
|
||||
DECODE_KEY(2, '2');
|
||||
DECODE_KEY(3, '3');
|
||||
DECODE_KEY(4, '4');
|
||||
DECODE_KEY(5, '5');
|
||||
DECODE_KEY(6, '6');
|
||||
DECODE_KEY(7, '7');
|
||||
DECODE_KEY(8, '8');
|
||||
DECODE_KEY(9, '9');
|
||||
DECODE_KEY(A, 'a');
|
||||
DECODE_KEY(B, 'b');
|
||||
DECODE_KEY(C, 'c');
|
||||
DECODE_KEY(D, 'd');
|
||||
DECODE_KEY(E, 'e');
|
||||
DECODE_KEY(F, 'f');
|
||||
DECODE_KEY(G, 'g');
|
||||
DECODE_KEY(H, 'h');
|
||||
DECODE_KEY(I, 'i');
|
||||
DECODE_KEY(J, 'j');
|
||||
DECODE_KEY(K, 'k');
|
||||
DECODE_KEY(L, 'l');
|
||||
DECODE_KEY(M, 'm');
|
||||
DECODE_KEY(N, 'n');
|
||||
DECODE_KEY(O, 'o');
|
||||
DECODE_KEY(P, 'p');
|
||||
DECODE_KEY(Q, 'q');
|
||||
DECODE_KEY(R, 'r');
|
||||
DECODE_KEY(S, 's');
|
||||
DECODE_KEY(T, 't');
|
||||
DECODE_KEY(U, 'u');
|
||||
DECODE_KEY(V, 'v');
|
||||
DECODE_KEY(W, 'w');
|
||||
DECODE_KEY(X, 'x');
|
||||
DECODE_KEY(Y, 'y');
|
||||
DECODE_KEY(Z, 'z');
|
||||
default:
|
||||
fprintf(stderr, "Unknown key code %d (?!)", event.code);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsing hook for each argument encountered. This populates an 'args' struct
|
||||
* and also verifies both that all necessary options are specified and that no
|
||||
* unknown arguments are offered.
|
||||
*/
|
||||
error_t
|
||||
parse_opt(int key, char *arg, struct argp_state *state)
|
||||
{
|
||||
args* cli_args = state->input;
|
||||
|
||||
// Verify that both arguments have values.
|
||||
switch (key) {
|
||||
// Device ID
|
||||
case 'd':
|
||||
cli_args->dev_id = arg;
|
||||
break;
|
||||
|
||||
// ZMQ Endpoint
|
||||
case 'e':
|
||||
cli_args->zmq_endpoint = arg;
|
||||
break;
|
||||
|
||||
// Don't support positional arguments.
|
||||
case ARGP_KEY_ARG:
|
||||
argp_usage(state);
|
||||
break;
|
||||
|
||||
// Verify arguments passed at the end.
|
||||
case ARGP_KEY_END:
|
||||
if (cli_args->dev_id == NULL || cli_args->zmq_endpoint == NULL)
|
||||
argp_usage(state);
|
||||
break;
|
||||
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to malloc `size` bytes. On failure, causes the program to exit.
|
||||
*/
|
||||
static void *
|
||||
Malloc(size_t size)
|
||||
{
|
||||
void *ptr = malloc(size);
|
||||
if (ptr == NULL) {
|
||||
fprintf(stderr, "Could not malloc %zu bytes!\n", size);
|
||||
exit(1);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to malloc `size` bytes. On failure, causes the program to exit.
|
||||
*/
|
||||
static void *
|
||||
Calloc(size_t size)
|
||||
{
|
||||
void *ptr = calloc(1, size);
|
||||
if (ptr == NULL) {
|
||||
fprintf(stderr, "Could not malloc %zu bytes!\n", size);
|
||||
exit(1);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* VARIOUS ll_string FUNCTIONS
|
||||
*/
|
||||
|
||||
static char *
|
||||
ll_string_to_c_string(ll_string *l)
|
||||
{
|
||||
ll_string *rest = l;
|
||||
unsigned int size = ll_string_len(l);
|
||||
char *c_string = Malloc(size + 1);
|
||||
|
||||
// NUL-terminate the C string.
|
||||
c_string[size] = '\x00';
|
||||
|
||||
// Copy the rest of the linked list into the C-string.
|
||||
for (int i = size; i > 0; i--)
|
||||
{
|
||||
c_string[i] = rest->character;
|
||||
rest = rest->next;
|
||||
}
|
||||
|
||||
return c_string;
|
||||
}
|
||||
|
||||
static ll_string *
|
||||
ll_string_append(ll_string *l, char c)
|
||||
{
|
||||
ll_string *new_head = Malloc(sizeof(ll_string));
|
||||
new_head->character = c;
|
||||
new_head->next = l;
|
||||
return new_head;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
ll_string_len(ll_string *l)
|
||||
{
|
||||
if (l == NULL) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1 + ll_string_len(l->next);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ll_string_free(ll_string *l)
|
||||
{
|
||||
if (l != NULL) {
|
||||
ll_string *next = l->next;
|
||||
free(l);
|
||||
ll_string_free(next);
|
||||
}
|
||||
}
|
83
barcode_service/barcode_service.h
Normal file
83
barcode_service/barcode_service.h
Normal file
@ -0,0 +1,83 @@
|
||||
#include <argp.h>
|
||||
|
||||
/* Barcodes are just strings. */
|
||||
typedef char* barcode;
|
||||
|
||||
/*
|
||||
* "Linked List String", a linked list of characters.
|
||||
* The head of the list is the last character in the string.
|
||||
*/
|
||||
typedef struct ll_string {
|
||||
char character;
|
||||
struct ll_string *next;
|
||||
} ll_string;
|
||||
|
||||
/*
|
||||
* Function prototypes
|
||||
*/
|
||||
error_t
|
||||
parse_opt(int key, char *arg, struct argp_state *state);
|
||||
|
||||
static barcode
|
||||
await_next_barcode(int fd);
|
||||
|
||||
static char
|
||||
decode_next_event(int fd);
|
||||
|
||||
static void *
|
||||
Calloc(size_t size);
|
||||
|
||||
static void *
|
||||
Malloc(size_t size);
|
||||
|
||||
static char *
|
||||
ll_string_to_c_string(ll_string *l);
|
||||
|
||||
static ll_string *
|
||||
ll_string_append(ll_string *l, char c);
|
||||
|
||||
static unsigned int
|
||||
ll_string_len(ll_string *l);
|
||||
|
||||
static void
|
||||
ll_string_free(ll_string *l);
|
||||
|
||||
/*
|
||||
* Arguments used by the read_scan application
|
||||
*/
|
||||
typedef struct args {
|
||||
char *zmq_endpoint;
|
||||
char *dev_id;
|
||||
} args;
|
||||
|
||||
/*
|
||||
* Descriptions of command line arguments we'd like to parse.
|
||||
*/
|
||||
static const struct argp_option options[] = {
|
||||
{
|
||||
.name = "zmq_endpoint",
|
||||
.key = 'e',
|
||||
.arg = "zmq_endpoint",
|
||||
.flags = 0,
|
||||
.doc = "ZeroMQ endpoint to publish barcodes",
|
||||
.group = 0
|
||||
},
|
||||
{
|
||||
.name = "dev_id",
|
||||
.key = 'd',
|
||||
.arg = "dev_id",
|
||||
.flags = 0,
|
||||
.doc = "Device id of the barcode scanner (in /dev/input/by-id)",
|
||||
.group = 0
|
||||
},
|
||||
// Need one zero-struct to terminate, per the argp spec.
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct argp argp = {
|
||||
.options = options,
|
||||
.parser = parse_opt,
|
||||
.args_doc = NULL,
|
||||
.doc = "Acquire barcode data and publish to a ZeroMQ channel\v",
|
||||
.children = NULL
|
||||
};
|
Loading…
Reference in New Issue
Block a user