Initial commit for ZMQ barcode service

This commit is contained in:
Andrew Brooks 2018-01-25 20:18:56 -06:00
parent a3a73ea1e8
commit 9e3b4d023c
3 changed files with 367 additions and 0 deletions

13
barcode_service/Makefile Normal file
View 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)

View 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);
}
}

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