From 9e3b4d023c58b64dd43d10edee54569d2773d2e4 Mon Sep 17 00:00:00 2001 From: Andrew Brooks Date: Thu, 25 Jan 2018 20:18:56 -0600 Subject: [PATCH] Initial commit for ZMQ barcode service --- barcode_service/Makefile | 13 ++ barcode_service/barcode_service.c | 271 ++++++++++++++++++++++++++++++ barcode_service/barcode_service.h | 83 +++++++++ 3 files changed, 367 insertions(+) create mode 100644 barcode_service/Makefile create mode 100644 barcode_service/barcode_service.c create mode 100644 barcode_service/barcode_service.h diff --git a/barcode_service/Makefile b/barcode_service/Makefile new file mode 100644 index 0000000..c9adc2d --- /dev/null +++ b/barcode_service/Makefile @@ -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) diff --git a/barcode_service/barcode_service.c b/barcode_service/barcode_service.c new file mode 100644 index 0000000..971a7c5 --- /dev/null +++ b/barcode_service/barcode_service.c @@ -0,0 +1,271 @@ +#include +#include +#include +#include +#include +#include + +#include + +#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); + } +} diff --git a/barcode_service/barcode_service.h b/barcode_service/barcode_service.h new file mode 100644 index 0000000..75d9277 --- /dev/null +++ b/barcode_service/barcode_service.h @@ -0,0 +1,83 @@ +#include + +/* 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 +};