/* * RadAngel gamma spectrometer USB HID device control and readout program * * gcc -std=gnu99 -Wall radangel.c -o radangel -pthread -lm -lhidapi-libusb * * strip -s radangel * * echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="04d8", ATTR{idProduct}=="0100", MODE="0666"' | sudo tee /etc/udev/rules.d/99-kromek-radangel.rules * */ #include #include #include #include #include #include #include #include #include // usleep() #define MAX_STR 255 #define MAX_CHANNEL 4096 #define QUEUESIZE 1024 #define DEV_VID 0x4d8 #define DEV_PID 0x100 #define LINE_MAX 4096 // non blocking #define HID_TIMEOUT -1 // 500 ms timeout //#define HID_TIMEOUT 500 typedef struct { struct timespec start_time; } config; typedef struct { FILE* file; char* name; } output; typedef struct { hid_device* handle; config config; output event; output stats; } context; typedef struct { struct timespec event_time; unsigned short channel; } data; typedef struct { data data[QUEUESIZE]; long head, tail; int full, empty; pthread_mutex_t *mutex; pthread_cond_t *not_full, *not_empty; context context; } queue; void usage(char* name); void info(const char* fmt, ...); void timespec_diff(struct timespec *start, struct timespec *stop, struct timespec *result); queue *queue_init(void); void queue_free(queue *q); void queue_add(queue *q, data src); void queue_del(queue *q, data *dst); void *event_source(void *args); void *event_sink(void *args); //void *update_context(void *args); static int verbose_flag; // Flag set by -v | --verbose void usage(char* name) { info("%s []\n\n", name); info("\t-h | --help Usage message.\n"); info("\t-v | --verbose Verbose output.\n"); info("\t-e | --event Event data file path.\n"); info("\t-s | --stats Stats data file path.\n"); info("\n"); } int main(int argc, char **argv) { queue *fifo = NULL; pthread_t pro, con; struct timespec start_time = { 0, 0 }; int c; int option_index = 0; fifo = queue_init(); if (fifo == NULL) { info("Error: main queue_init() failed.\n"); exit(1); } fifo->context.event.name = "-"; fifo->context.event.file = stdout; fifo->context.stats.name = NULL; fifo->context.stats.file = NULL; while (1) { static struct option long_options[] = { {"verbose", no_argument, &verbose_flag, 1 }, {"help", no_argument, NULL, 'h'}, {"event", required_argument, NULL, 'e'}, {"stats", required_argument, NULL, 's'}, {0, 0, 0, 0 } // end of options }; option_index = 0; c = getopt_long(argc, argv, "he:s:", long_options, &option_index); if (c == -1) { // end of options break; } switch (c) { case 0: if (long_options[option_index].flag != 0) { // option sets a flag break; } info("option %s", long_options[option_index].name); if (optarg) { info(" with arg %s", optarg); } info("\n"); break; case 'e': fifo->context.event.name = optarg; break; case 's': fifo->context.stats.name = optarg; break; case 'h': usage(argv[0]); queue_free(fifo); exit(1); break; case '?': // getopt_long already printed an error message. break; default: abort(); } } if (optind < argc) { // remaining command line arguments (not options). info("unknown argument: "); while (optind < argc) { info("%s ", argv[optind++]); } info("\n"); queue_free(fifo); exit(1); } // ensure input if (hid_init()) { info("Error: unable to initialize hidapi.\n"); queue_free(fifo); exit(1); } // open device later in read thread, might not be connected yet // measurement start clock_gettime(CLOCK_MONOTONIC, &start_time); fifo->context.config.start_time = start_time; // start the producer and consumer threads pthread_create(&pro, NULL, event_source, fifo); pthread_create(&con, NULL, event_sink, fifo); pthread_join(pro, NULL); pthread_join(con, NULL); if (fifo->context.event.file != NULL) { fclose(fifo->context.event.file); } if (fifo->context.stats.file != NULL) { fclose(fifo->context.stats.file); } if (fifo->context.handle != NULL) { hid_close(fifo->context.handle); } hid_exit(); // Free static HIDAPI objects queue_free(fifo); return 0; } void info(const char* fmt, ...) { va_list args; va_start(args, fmt); if (verbose_flag) { fprintf(stderr, fmt, args); } va_end(args); } queue *queue_init(void) { queue *q; q = (queue *) malloc (sizeof (queue)); if (q == NULL) return (NULL); q->empty = 1; q->full = 0; q->head = 0; q->tail = 0; q->mutex = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t)); pthread_mutex_init(q->mutex, NULL); q->not_full = (pthread_cond_t *) malloc (sizeof (pthread_cond_t)); pthread_cond_init(q->not_full, NULL); q->not_empty = (pthread_cond_t *) malloc (sizeof (pthread_cond_t)); pthread_cond_init(q->not_empty, NULL); return (q); } void queue_free(queue *q) { pthread_mutex_destroy(q->mutex); free(q->mutex); pthread_cond_destroy(q->not_full); free(q->not_full); pthread_cond_destroy(q->not_empty); free(q->not_empty); free(q); } void queue_add(queue *q, data src) { q->data[q->tail] = src; q->tail++; if (q->tail == QUEUESIZE) { q->tail = 0; } if (q->tail == q->head) { q->full = 1; } q->empty = 0; return; } void queue_del(queue *q, data *dst) { *dst = q->data[q->head]; q->head++; if (q->head == QUEUESIZE) { q->head = 0; } if (q->head == q->tail) { q->empty = 1; } q->full = 0; return; } void *event_source(void *q) { queue *fifo; fifo = (queue *)q; data data; hid_device* handle = fifo->context.handle; unsigned char buffer[64]; int result; struct timespec event_time = { 0, 0L }; struct timespec sleep_time = { 0, 500000000L}; // retry time do { // read events loop while (handle == NULL) { // try to aquire handle handle = hid_open(DEV_VID, DEV_PID, NULL); if (handle != NULL) { fifo->context.handle = handle; } else { nanosleep(&sleep_time, NULL); // wait to retry } } while (handle != NULL) { // try to read data memset(buffer, 0, sizeof(buffer)); result = hid_read_timeout(handle, buffer, sizeof(buffer), HID_TIMEOUT); if (result > 0) { // got some bytes break; } else if (result < 0) { // something went wrong: disconnect? hid_close(handle); handle = NULL; // invalidate handle for reaquire fifo->context.handle = handle; } } clock_gettime(CLOCK_MONOTONIC, &event_time); if (buffer[0] == 4) { // got an event data.channel = (((buffer[1] << 8) | buffer[2]) >> 4); data.event_time = event_time; // lock the queue, wait for space, push pthread_mutex_lock(fifo->mutex); while (fifo->full) { // queue is full, wait for consumer pthread_cond_wait(fifo->not_full, fifo->mutex); // unlocks, block, relock } queue_add(fifo, data); pthread_mutex_unlock(fifo->mutex); pthread_cond_signal(fifo->not_empty); } } while (1); // read events loop return (NULL); } void timespec_diff(struct timespec *start, struct timespec *stop, struct timespec *result) { if ((stop->tv_nsec - start->tv_nsec) < 0) { result->tv_sec = stop->tv_sec - start->tv_sec - 1; result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000; } else { result->tv_sec = stop->tv_sec - start->tv_sec; result->tv_nsec = stop->tv_nsec - start->tv_nsec; } return; } void *event_sink(void *q) { queue *fifo; fifo = (queue *)q; data data; char *events_filename = fifo->context.event.name; char *stats_filename = fifo->context.stats.name; char *events_filemode = "a+"; char *stats_filemode = "w"; FILE *events_file = events_filename ? ((strncmp(events_filename, "-", 1) == 0) ? stdout : fopen(events_filename, events_filemode)) : NULL; FILE *stats_file = stats_filename ? ((strncmp(stats_filename, "-", 1) == 0) ? stdout : fopen(stats_filename, stats_filemode )) : NULL; fifo->context.event.file = events_file; fifo->context.stats.file = stats_file; struct timespec start_time = fifo->context.config.start_time; struct timespec event_time = { 0, 0L }; struct timespec sleep_time = { 0, 500000000L}; // retry time long int tv_sec; unsigned long int tv_nsec; unsigned int channel = 0; unsigned long histogram[MAX_CHANNEL]; memset(histogram, 0, MAX_CHANNEL * sizeof(unsigned long)); const char event_header[] = "time\tchannel\n"; const char event_format[] = "%ld.%03ld\t%d\n"; const char stats_header[] = "channel\n"; const char stats_format[] = "%ld\n"; char line[LINE_MAX]; int i, result = 1; // reconstruct event_time and histogram if (events_filename && events_file && (events_file != stdout)) { rewind(events_file); while (fgets(line, LINE_MAX, events_file) != NULL) { if(sscanf(line, event_format, &tv_sec, &tv_nsec, &channel) == 3) { event_time.tv_sec = tv_sec; event_time.tv_nsec = tv_nsec; histogram[channel % MAX_CHANNEL] += 1; // update histogram } } } if (events_filename && events_file && ((events_file == stdout) || (ftell(events_file) == 0))) { fprintf(events_file, event_header); } // readjust start_time timespec_diff(&event_time, &start_time, &start_time); fifo->context.config.start_time = start_time; if (stats_file && stats_filename) { stats_file = (strncmp(stats_filename, "-", 1) == 0) ? stdout : freopen(stats_filename, stats_filemode, stats_file); fifo->context.stats.file = stats_file; result = !!stats_file; if (stats_file) { result = fprintf(stats_file, stats_header); for (i = 0; i < MAX_CHANNEL; i++) { result = result && (fprintf(stats_file, stats_format, histogram[i] ) > 0); } fflush(stats_file); } } do { // output loop // lock the queue, wait for data, pull pthread_mutex_lock(fifo->mutex); while (fifo->empty) { // queue is empty, wait for producer pthread_cond_wait(fifo->not_empty, fifo->mutex); // unlock, block, relock } queue_del(fifo, &data); pthread_mutex_unlock (fifo->mutex); pthread_cond_signal(fifo->not_full); // event time since measurement start event_time = data.event_time; channel = data.channel; timespec_diff(&start_time, &event_time, &event_time); // update histogram histogram[channel % MAX_CHANNEL] += 1; if (events_file) { result = (fprintf(events_file, event_format, event_time.tv_sec, event_time.tv_nsec / 1000000, channel ) > 0); fflush(events_file); } else if (events_filename) {// try reopen nanosleep(&sleep_time, NULL); // wait to retry events_file = (strncmp(events_filename, "-", 1) == 0) ? stdout : freopen(events_filename, events_filemode, events_file); fifo->context.event.file = events_file; result = !!events_file; } if (stats_file && stats_filename) { stats_file = (strncmp(stats_filename, "-", 1) == 0) ? stdout : freopen(stats_filename, stats_filemode, stats_file); fifo->context.stats.file = stats_file; result = !!stats_file; if (stats_file) { for (i = 0; i < MAX_CHANNEL; i++) { result = result && (fprintf(stats_file, stats_format, histogram[i] ) > 0); } fflush(stats_file); } } } while (result); // output loop return (NULL); } /* void *update_context (void *q) { queue *fifo; fifo = (queue *)q; return (NULL); } */